am 4fea5373: Merge "Add WEBP to the list of Image formats that support Compression."

* commit '4fea5373d350f5d855408ffaa41206b8fa743c55':
  Add WEBP to the list of Image formats that support Compression.
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index d764aa9..f2b6fce 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -30,14 +30,15 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.net.Uri;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.provider.Settings;
 
 import java.io.File;
 import java.lang.reflect.Field;
@@ -223,7 +224,7 @@
         String filter = nextArg();
 
         try {
-            List<PackageInfo> packages = mPm.getInstalledPackages(getFlags);
+            final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags);
 
             int count = packages.size();
             for (int p = 0 ; p < count ; p++) {
@@ -247,6 +248,22 @@
         }
     }
 
+    @SuppressWarnings("unchecked")
+    private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags)
+            throws RemoteException {
+        final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>();
+        PackageInfo lastItem = null;
+        ParceledListSlice<PackageInfo> slice;
+
+        do {
+            final String lastKey = lastItem != null ? lastItem.packageName : null;
+            slice = pm.getInstalledPackages(flags, lastKey);
+            lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR);
+        } while (!slice.isLastSlice());
+
+        return packageInfos;
+    }
+
     /**
      * Lists all of the features supported by the current device.
      *
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0e473c9..fe4bfcf 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -48,6 +48,7 @@
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
@@ -83,6 +84,7 @@
 import android.os.IBinder;
 import android.os.IPowerManager;
 import android.os.Looper;
+import android.os.Parcel;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -1998,19 +2000,41 @@
             throw new NameNotFoundException("No shared userid for user:"+sharedUserName);
         }
 
+        @SuppressWarnings("unchecked")
         @Override
         public List<PackageInfo> getInstalledPackages(int flags) {
             try {
-                return mPM.getInstalledPackages(flags);
+                final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>();
+                PackageInfo lastItem = null;
+                ParceledListSlice<PackageInfo> slice;
+
+                do {
+                    final String lastKey = lastItem != null ? lastItem.packageName : null;
+                    slice = mPM.getInstalledPackages(flags, lastKey);
+                    lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR);
+                } while (!slice.isLastSlice());
+
+                return packageInfos;
             } catch (RemoteException e) {
                 throw new RuntimeException("Package manager has died", e);
             }
         }
 
+        @SuppressWarnings("unchecked")
         @Override
         public List<ApplicationInfo> getInstalledApplications(int flags) {
             try {
-                return mPM.getInstalledApplications(flags);
+                final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>();
+                ApplicationInfo lastItem = null;
+                ParceledListSlice<ApplicationInfo> slice;
+
+                do {
+                    final String lastKey = lastItem != null ? lastItem.packageName : null;
+                    slice = mPM.getInstalledApplications(flags, lastKey);
+                    lastItem = slice.populateList(applicationInfos, ApplicationInfo.CREATOR);
+                } while (!slice.isLastSlice());
+
+                return applicationInfos;
             } catch (RemoteException e) {
                 throw new RuntimeException("Package manager has died", e);
             }
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 7e5f858..8218c03 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -269,6 +269,22 @@
         }
     }
 
+    /**
+     * Allow or disallow incoming connection
+     * @param device Sink
+     * @param value True / False
+     * @return Success or Failure of the binder call.
+     */
+    public boolean allowIncomingConnect(BluetoothDevice device, boolean value) {
+        if (DBG) log("allowIncomingConnect(" + device + ":" + value + ")");
+        try {
+            return mService.allowIncomingConnect(device, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
     /** Helper for converting a state to a string.
      * For debug use only - strings are not internationalized.
      * @hide
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index aee6ad8..e67ace0 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -276,6 +276,33 @@
     public static final String ACTION_PAIRING_CANCEL =
             "android.bluetooth.device.action.PAIRING_CANCEL";
 
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_ACCESS_REQUEST =
+            "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST";
+
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_ACCESS_REPLY =
+            "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY";
+
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_ACCESS_CANCEL =
+            "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL";
+    /**
+     * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intent.
+     * @hide
+     */
+    public static final String EXTRA_CONNECTION_ACCESS_RESULT =
+        "android.bluetooth.device.extra.CONNECTION_ACCESS_RESULT";
+
+    /**@hide*/
+    public static final int CONNECTION_ACCESS_YES = 1;
+
+    /**@hide*/
+    public static final int CONNECTION_ACCESS_NO = 2;
+
     /** A bond attempt succeeded
      * @hide */
     public static final int BOND_SUCCESS = 0;
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index 9d7e641..8896451 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -21,9 +21,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Message;
+import android.os.PowerManager;
 import android.server.BluetoothA2dpService;
 import android.server.BluetoothService;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.util.HierarchicalState;
 import com.android.internal.util.HierarchicalStateMachine;
@@ -73,9 +75,17 @@
     public static final int AUTO_CONNECT_PROFILES = 101;
     public static final int TRANSITION_TO_STABLE = 102;
     public static final int CONNECT_OTHER_PROFILES = 103;
+    private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104;
+    private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105;
 
     private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
     private static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs
+    private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs
+    private static final int CONNECTION_ACCESS_UNDEFINED = -1;
+    private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec
+    private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours
+
+    private static final String PREFS_NAME = "ConnectionAccess";
 
     private BondedDevice mBondedDevice = new BondedDevice();
     private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
@@ -90,10 +100,16 @@
     private BluetoothPbap     mPbapService;
     private boolean mHeadsetServiceConnected;
     private boolean mPbapServiceConnected;
+    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
 
     private BluetoothDevice mDevice;
     private int mHeadsetState;
     private int mA2dpState;
+    private long mIncomingRejectTimer;
+    private boolean mConnectionAccessReplyReceived = false;
+    private Pair<Integer, String> mIncomingConnections;
+    private PowerManager.WakeLock mWakeLock;
+    private PowerManager mPowerManager;
 
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -108,6 +124,10 @@
                 int initiator = intent.getIntExtra(
                     BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
                     BluetoothHeadset.LOCAL_DISCONNECT);
+                // We trust this device now
+                if (newState == BluetoothHeadset.STATE_CONNECTED) {
+                    setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
+                }
                 mHeadsetState = newState;
                 if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
                     initiator == BluetoothHeadset.REMOTE_DISCONNECT) {
@@ -121,6 +141,10 @@
                 int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
                 int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0);
                 mA2dpState = newState;
+                // We trust this device now
+                if (newState == BluetoothA2dp.STATE_CONNECTED) {
+                    setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
+                }
                 if ((oldState == BluetoothA2dp.STATE_CONNECTED ||
                            oldState == BluetoothA2dp.STATE_PLAYING) &&
                            newState == BluetoothA2dp.STATE_DISCONNECTED) {
@@ -134,6 +158,13 @@
                 // This is technically not needed, but we can get stuck sometimes.
                 // For example, if incoming A2DP fails, we are not informed by Bluez
                 sendMessage(TRANSITION_TO_STABLE);
+            } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+                mWakeLock.release();
+                int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+                                             BluetoothDevice.CONNECTION_ACCESS_NO);
+                Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY);
+                msg.arg1 = val;
+                sendMessage(msg);
             }
       }
     };
@@ -174,11 +205,20 @@
         filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+        filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
 
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
         HeadsetServiceListener l = new HeadsetServiceListener();
         PbapServiceListener p = new PbapServiceListener();
+
+        mIncomingConnections = mService.getIncomingState(address);
+        mIncomingRejectTimer = readTimerValue();
+        mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
+                                              PowerManager.ACQUIRE_CAUSES_WAKEUP |
+                                              PowerManager.ON_AFTER_RELEASE, TAG);
+        mWakeLock.setReferenceCounted(false);
     }
 
     private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
@@ -438,6 +478,24 @@
                     // Ignore
                     Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
                     break;
+                case CONNECTION_ACCESS_REQUEST_REPLY:
+                    int val = message.arg1;
+                    mConnectionAccessReplyReceived = true;
+                    boolean value = false;
+                    if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
+                        value = true;
+                    }
+                    setTrust(val);
+
+                    handleIncomingConnection(CONNECT_HFP_INCOMING, value);
+                    break;
+                case CONNECTION_ACCESS_REQUEST_EXPIRY:
+                    if (!mConnectionAccessReplyReceived) {
+                        handleIncomingConnection(CONNECT_HFP_INCOMING, false);
+                        sendConnectionAccessRemovalIntent();
+                        sendMessage(TRANSITION_TO_STABLE);
+                    }
+                    break;
                 case CONNECT_A2DP_INCOMING:
                     // Serialize the commands.
                     deferMessage(message);
@@ -607,6 +665,25 @@
                 case CONNECT_A2DP_INCOMING:
                     // ignore
                     break;
+                case CONNECTION_ACCESS_REQUEST_REPLY:
+                    int val = message.arg1;
+                    mConnectionAccessReplyReceived = true;
+                    boolean value = false;
+                    if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
+                        value = true;
+                    }
+                    setTrust(val);
+                    handleIncomingConnection(CONNECT_A2DP_INCOMING, value);
+                    break;
+                case CONNECTION_ACCESS_REQUEST_EXPIRY:
+                    // The check protects the race condition between REQUEST_REPLY
+                    // and the timer expiry.
+                    if (!mConnectionAccessReplyReceived) {
+                        handleIncomingConnection(CONNECT_A2DP_INCOMING, false);
+                        sendConnectionAccessRemovalIntent();
+                        sendMessage(TRANSITION_TO_STABLE);
+                    }
+                    break;
                 case CONNECT_A2DP_OUTGOING:
                     // Defer message and retry
                     deferMessage(message);
@@ -662,8 +739,138 @@
         deferMessage(msg);
     }
 
+    private void updateIncomingAllowedTimer() {
+        // Not doing a perfect exponential backoff because
+        // we want two different rates. For all practical
+        // purposes, this is good enough.
+        if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER;
+
+        mIncomingRejectTimer *= 5;
+        if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) {
+            mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER;
+        }
+        writeTimerValue(mIncomingRejectTimer);
+    }
+
+    private boolean handleIncomingConnection(int command, boolean accept) {
+        boolean ret = false;
+        Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept);
+        switch (command) {
+            case CONNECT_HFP_INCOMING:
+                if (!accept) {
+                    ret = mHeadsetService.rejectIncomingConnect(mDevice);
+                    sendMessage(TRANSITION_TO_STABLE);
+                    updateIncomingAllowedTimer();
+                } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
+                    writeTimerValue(0);
+                    ret =  mHeadsetService.acceptIncomingConnect(mDevice);
+                } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
+                    writeTimerValue(0);
+                    handleConnectionOfOtherProfiles(command);
+                    ret = mHeadsetService.createIncomingConnect(mDevice);
+                }
+                break;
+            case CONNECT_A2DP_INCOMING:
+                if (!accept) {
+                    ret = mA2dpService.allowIncomingConnect(mDevice, false);
+                    sendMessage(TRANSITION_TO_STABLE);
+                    updateIncomingAllowedTimer();
+                } else {
+                    writeTimerValue(0);
+                    ret = mA2dpService.allowIncomingConnect(mDevice, true);
+                    handleConnectionOfOtherProfiles(command);
+                }
+                break;
+            default:
+                Log.e(TAG, "Waiting for incoming connection but state changed to:" + command);
+                break;
+       }
+       return ret;
+    }
+
+    private void sendConnectionAccessIntent() {
+        mConnectionAccessReplyReceived = false;
+
+        if (!mPowerManager.isScreenOn()) mWakeLock.acquire();
+
+        Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+    }
+
+    private void sendConnectionAccessRemovalIntent() {
+        mWakeLock.release();
+        Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+        mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+    }
+
+    private int getTrust() {
+        String address = mDevice.getAddress();
+        if (mIncomingConnections != null) return mIncomingConnections.first;
+        return CONNECTION_ACCESS_UNDEFINED;
+    }
+
+
+    private String getStringValue(long value) {
+        StringBuilder sbr = new StringBuilder();
+        sbr.append(Long.toString(System.currentTimeMillis()));
+        sbr.append("-");
+        sbr.append(Long.toString(value));
+        return sbr.toString();
+    }
+
+    private void setTrust(int value) {
+        String second;
+        if (mIncomingConnections == null) {
+            second = getStringValue(INIT_INCOMING_REJECT_TIMER);
+        } else {
+            second = mIncomingConnections.second;
+        }
+
+        mIncomingConnections = new Pair(value, second);
+        mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
+    }
+
+    private void writeTimerValue(long value) {
+        Integer first;
+        if (mIncomingConnections == null) {
+            first = CONNECTION_ACCESS_UNDEFINED;
+        } else {
+            first = mIncomingConnections.first;
+        }
+        mIncomingConnections = new Pair(first, getStringValue(value));
+        mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
+    }
+
+    private long readTimerValue() {
+        if (mIncomingConnections == null)
+            return 0;
+        String value = mIncomingConnections.second;
+        String[] splits = value.split("-");
+        if (splits != null && splits.length == 2) {
+            return Long.parseLong(splits[1]);
+        }
+        return 0;
+    }
+
+    private boolean readIncomingAllowedValue() {
+        if (readTimerValue() == 0) return true;
+        String value = mIncomingConnections.second;
+        String[] splits = value.split("-");
+        if (splits != null && splits.length == 2) {
+            long val1 = Long.parseLong(splits[0]);
+            long val2 = Long.parseLong(splits[1]);
+            if (val1 + val2 <= System.currentTimeMillis()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     synchronized boolean processCommand(int command) {
-        Log.i(TAG, "Processing command:" + command);
+        Log.e(TAG, "Processing command:" + command);
+        Message msg;
         switch(command) {
             case  CONNECT_HFP_OUTGOING:
                 if (mHeadsetService != null) {
@@ -673,11 +880,21 @@
             case CONNECT_HFP_INCOMING:
                 if (!mHeadsetServiceConnected) {
                     deferProfileServiceMessage(command);
-                } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
-                    return mHeadsetService.acceptIncomingConnect(mDevice);
-                } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
-                    handleConnectionOfOtherProfiles(command);
-                    return mHeadsetService.createIncomingConnect(mDevice);
+                } else {
+                    // Check if device is already trusted
+                    int access = getTrust();
+                    if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
+                        handleIncomingConnection(command, true);
+                    } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO &&
+                               !readIncomingAllowedValue()) {
+                        handleIncomingConnection(command, false);
+                    } else {
+                        sendConnectionAccessIntent();
+                        msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY);
+                        sendMessageDelayed(msg,
+                                CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT);
+                    }
+                    return true;
                 }
                 break;
             case CONNECT_A2DP_OUTGOING:
@@ -686,8 +903,19 @@
                 }
                 break;
             case CONNECT_A2DP_INCOMING:
-                handleConnectionOfOtherProfiles(command);
-                // ignore, Bluez takes care
+                // Check if device is already trusted
+                int access = getTrust();
+                if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
+                    handleIncomingConnection(command, true);
+                } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO &&
+                           !readIncomingAllowedValue()) {
+                    handleIncomingConnection(command, false);
+                } else {
+                    sendConnectionAccessIntent();
+                    msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY);
+                    sendMessageDelayed(msg,
+                            CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT);
+                }
                 return true;
             case DISCONNECT_HFP_OUTGOING:
                 if (!mHeadsetServiceConnected) {
@@ -728,6 +956,8 @@
                 }
                 break;
             case UNPAIR:
+                writeTimerValue(INIT_INCOMING_REJECT_TIMER);
+                setTrust(CONNECTION_ACCESS_UNDEFINED);
                 return mService.removeBondInternal(mDevice.getAddress());
             default:
                 Log.e(TAG, "Error: Unknown Command");
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index da1aa45..f64f826 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -494,6 +494,23 @@
     }
 
     /**
+     * Reject the incoming connection.
+     * @hide
+     */
+    public boolean rejectIncomingConnect(BluetoothDevice device) {
+        if (DBG) log("rejectIncomingConnect");
+        if (mService != null) {
+            try {
+                return mService.rejectIncomingConnect(device);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
      * Connect to a Bluetooth Headset.
      * Note: This is an internal function and shouldn't be exposed
      * @hide
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index 40f1058..0c2cf1b 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -36,4 +36,6 @@
 
     boolean connectSinkInternal(in BluetoothDevice device);
     boolean disconnectSinkInternal(in BluetoothDevice device);
+    boolean allowIncomingConnect(in BluetoothDevice device, boolean value);
+
 }
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index d96f0ca..62bceee 100644
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -37,6 +37,7 @@
 
     boolean createIncomingConnect(in BluetoothDevice device);
     boolean acceptIncomingConnect(in BluetoothDevice device);
+    boolean rejectIncomingConnect(in BluetoothDevice device);
     boolean cancelConnectThread();
     boolean connectHeadsetInternal(in BluetoothDevice device);
     boolean disconnectHeadsetInternal(in BluetoothDevice device);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index bfc9185..5df5eba 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -30,6 +30,7 @@
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ProviderInfo;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
@@ -109,9 +110,21 @@
     List<ResolveInfo> queryIntentServices(in Intent intent,
             String resolvedType, int flags);
 
-    List<PackageInfo> getInstalledPackages(int flags);
+    /**
+     * This implements getInstalledPackages via a "last returned row"
+     * mechanism that is not exposed in the API. This is to get around the IPC
+     * limit that kicks in when flags are included that bloat up the data
+     * returned.
+     */
+    ParceledListSlice getInstalledPackages(int flags, in String lastRead);
 
-    List<ApplicationInfo> getInstalledApplications(int flags);
+    /**
+     * This implements getInstalledApplications via a "last returned row"
+     * mechanism that is not exposed in the API. This is to get around the IPC
+     * limit that kicks in when flags are included that bloat up the data
+     * returned.
+     */
+    ParceledListSlice getInstalledApplications(int flags, in String lastRead);
 
     /**
      * Retrieve all applications that are marked as persistent.
diff --git a/core/java/android/content/pm/ParceledListSlice.aidl b/core/java/android/content/pm/ParceledListSlice.aidl
new file mode 100755
index 0000000..c02cc6a
--- /dev/null
+++ b/core/java/android/content/pm/ParceledListSlice.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2011, 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.content.pm;
+
+parcelable ParceledListSlice;
diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java
new file mode 100644
index 0000000..f3a98db4
--- /dev/null
+++ b/core/java/android/content/pm/ParceledListSlice.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011 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.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Builds up a parcel that is discarded when written to another parcel or
+ * written to a list. This is useful for API that sends huge lists across a
+ * Binder that may be larger than the IPC limit.
+ *
+ * @hide
+ */
+public class ParceledListSlice<T extends Parcelable> implements Parcelable {
+    /*
+     * TODO get this number from somewhere else. For now set it to a quarter of
+     * the 1MB limit.
+     */
+    private static final int MAX_IPC_SIZE = 256 * 1024;
+
+    private Parcel mParcel;
+
+    private int mNumItems;
+
+    private boolean mIsLastSlice;
+
+    public ParceledListSlice() {
+        mParcel = Parcel.obtain();
+    }
+
+    private ParceledListSlice(Parcel p, int numItems, boolean lastSlice) {
+        mParcel = p;
+        mNumItems = numItems;
+        mIsLastSlice = lastSlice;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Write this to another Parcel. Note that this discards the internal Parcel
+     * and should not be used anymore. This is so we can pass this to a Binder
+     * where we won't have a chance to call recycle on this.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mNumItems);
+        dest.writeInt(mIsLastSlice ? 1 : 0);
+
+        if (mNumItems > 0) {
+            final int parcelSize = mParcel.dataSize();
+            dest.writeInt(parcelSize);
+            dest.appendFrom(mParcel, 0, parcelSize);
+        }
+
+        mNumItems = 0;
+        mParcel.recycle();
+        mParcel = null;
+    }
+
+    /**
+     * Appends a parcel to this list slice.
+     *
+     * @param item Parcelable item to append to this list slice
+     * @return true when the list slice is full and should not be appended to
+     *         anymore
+     */
+    public boolean append(T item) {
+        if (mParcel == null) {
+            throw new IllegalStateException("ParceledListSlice has already been recycled");
+        }
+
+        item.writeToParcel(mParcel, PARCELABLE_WRITE_RETURN_VALUE);
+        mNumItems++;
+
+        return mParcel.dataSize() > MAX_IPC_SIZE;
+    }
+
+    /**
+     * Populates a list and discards the internal state of the
+     * ParceledListSlice in the process. The instance should
+     * not be used anymore.
+     *
+     * @param list list to insert items from this slice.
+     * @param creator creator that knows how to unparcel the
+     *        target object type.
+     * @return the last item inserted into the list or null if none.
+     */
+    public T populateList(List<T> list, Creator<T> creator) {
+        mParcel.setDataPosition(0);
+
+        T item = null;
+        for (int i = 0; i < mNumItems; i++) {
+            item = creator.createFromParcel(mParcel);
+            list.add(item);
+        }
+
+        mParcel.recycle();
+        mParcel = null;
+
+        return item;
+    }
+
+    /**
+     * Sets whether this is the last list slice in the series.
+     *
+     * @param lastSlice
+     */
+    public void setLastSlice(boolean lastSlice) {
+        mIsLastSlice = lastSlice;
+    }
+
+    /**
+     * Returns whether this is the last slice in a series of slices.
+     *
+     * @return true if this is the last slice in the series.
+     */
+    public boolean isLastSlice() {
+        return mIsLastSlice;
+    }
+
+    @SuppressWarnings("unchecked")
+    public static final Parcelable.Creator<ParceledListSlice> CREATOR =
+            new Parcelable.Creator<ParceledListSlice>() {
+        public ParceledListSlice createFromParcel(Parcel in) {
+            final int numItems = in.readInt();
+            final boolean lastSlice = in.readInt() == 1;
+
+            if (numItems > 0) {
+                final int parcelSize = in.readInt();
+
+                // Advance within this Parcel
+                int offset = in.dataPosition();
+                in.setDataPosition(offset + parcelSize);
+
+                Parcel p = Parcel.obtain();
+                p.setDataPosition(0);
+                p.appendFrom(in, offset, parcelSize);
+                p.setDataPosition(0);
+
+                return new ParceledListSlice(p, numItems, lastSlice);
+            } else {
+                return new ParceledListSlice();
+            }
+        }
+
+        public ParceledListSlice[] newArray(int size) {
+            return new ParceledListSlice[size];
+        }
+    };
+}
diff --git a/core/java/android/nfc/INfcAdapterExtras.aidl b/core/java/android/nfc/INfcAdapterExtras.aidl
index 8677a50..0c2a2fd 100755
--- a/core/java/android/nfc/INfcAdapterExtras.aidl
+++ b/core/java/android/nfc/INfcAdapterExtras.aidl
@@ -16,7 +16,6 @@
 
 package android.nfc;
 
-import android.nfc.ApduList;
 import android.os.Bundle;
 
 
@@ -29,6 +28,5 @@
     Bundle transceive(in byte[] data_in);
     int getCardEmulationRoute();
     void setCardEmulationRoute(int route);
-    void registerTearDownApdus(String packageName, in ApduList apdu);
-    void unregisterTearDownApdus(String packageName);
+    void authenticate(in byte[] token);
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 38c5dc4..75bdc2b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2600,27 +2600,6 @@
         public static final String WIFI_SAVED_STATE = "wifi_saved_state";
 
         /**
-         * AP SSID
-         *
-         * @hide
-         */
-        public static final String WIFI_AP_SSID = "wifi_ap_ssid";
-
-        /**
-         * AP security
-         *
-         * @hide
-         */
-        public static final String WIFI_AP_SECURITY = "wifi_ap_security";
-
-        /**
-         * AP passphrase
-         *
-         * @hide
-         */
-        public static final String WIFI_AP_PASSWD = "wifi_ap_passwd";
-
-        /**
          * The acceptable packet loss percentage (range 0 - 100) before trying
          * another AP on the same network.
          */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 62f66b6..2e480e8 100755
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -542,7 +542,7 @@
              * values:</p>
              *
              * <ul>
-             *   <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs
+             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
              *   that make up the message.</li>
              * </ul>
              *
@@ -586,6 +586,46 @@
                     "android.provider.Telephony.WAP_PUSH_RECEIVED";
 
             /**
+             * Broadcast Action: A new Cell Broadcast message has been received
+             * by the device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
+             *   that make up the message.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_CB_RECEIVED_ACTION =
+                    "android.provider.Telephony.SMS_CB_RECEIVED";
+
+            /**
+             * Broadcast Action: A new Emergency Broadcast message has been received
+             * by the device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
+             *   that make up the message.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_EMERGENCY_CB_RECEIVED_ACTION =
+                    "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
+
+            /**
              * Broadcast Action: The SIM storage for SMS messages is full.  If
              * space is not freed, messages targeted for the SIM (class 2) may
              * not be saved.
@@ -618,7 +658,7 @@
              * @param intent the intent to read from
              * @return an array of SmsMessages for the PDUs
              */
-            public static final SmsMessage[] getMessagesFromIntent(
+            public static SmsMessage[] getMessagesFromIntent(
                     Intent intent) {
                 Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
                 byte[][] pduObjs = new byte[messages.length][];
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 6e221c8..bea01f3 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -457,6 +457,22 @@
                 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
     }
 
+    public synchronized boolean allowIncomingConnect(BluetoothDevice device, boolean value) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        String address = device.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            return false;
+        }
+        Integer data = mBluetoothService.getAuthorizationAgentRequestData(address);
+        if (data == null) {
+            Log.w(TAG, "allowIncomingConnect(" + device + ") called but no native data available");
+            return false;
+        }
+        log("allowIncomingConnect: A2DP: " + device + ":" + value);
+        return mBluetoothService.setAuthorizationNative(address, value, data.intValue());
+    }
+
     private synchronized void onSinkPropertyChanged(String path, String []propValues) {
         if (!mBluetoothService.isEnabled()) {
             return;
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 568d465..5cc9144 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -50,6 +50,7 @@
     private boolean mInterrupted;
 
     private final HashMap<String, Integer> mPasskeyAgentRequestData;
+    private final HashMap<String, Integer> mAuthorizationAgentRequestData;
     private final BluetoothService mBluetoothService;
     private final BluetoothAdapter mAdapter;
     private final Context mContext;
@@ -109,6 +110,7 @@
         mBluetoothService = bluetoothService;
         mContext = context;
         mPasskeyAgentRequestData = new HashMap();
+        mAuthorizationAgentRequestData = new HashMap<String, Integer>();
         mAdapter = adapter;
         //WakeLock instantiation in BluetoothEventLoop class
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -130,6 +132,10 @@
         return mPasskeyAgentRequestData;
     }
 
+    /* package */ HashMap<String, Integer> getAuthorizationAgentRequestData() {
+        return mAuthorizationAgentRequestData;
+    }
+
     /* package */ void start() {
 
         if (!isEventLoopRunningNative()) {
@@ -518,27 +524,29 @@
         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
     }
 
-    private boolean onAgentAuthorize(String objectPath, String deviceUuid) {
+    private void  onAgentAuthorize(String objectPath, String deviceUuid, int nativeData) {
         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
         if (address == null) {
             Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize");
-            return false;
+            return;
         }
 
         boolean authorized = false;
         ParcelUuid uuid = ParcelUuid.fromString(deviceUuid);
         BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
 
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        mAuthorizationAgentRequestData.put(address, new Integer(nativeData));
+
         // Bluez sends the UUID of the local service being accessed, _not_ the
         // remote service
         if (mBluetoothService.isEnabled() &&
                 (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
                         || BluetoothUuid.isAdvAudioDist(uuid)) &&
                         !isOtherSinkInNonDisconnectingState(address)) {
-            BluetoothDevice device = mAdapter.getRemoteDevice(address);
             authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
             if (authorized) {
-                Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address);
+                Log.i(TAG, "First check pass for incoming A2DP / AVRCP connection from " + address);
                 // Some headsets try to connect AVCTP before AVDTP - against the recommendation
                 // If AVCTP connection fails, we get stuck in IncomingA2DP state in the state
                 // machine.  We don't handle AVCTP signals currently. We only send
@@ -546,6 +554,8 @@
                 // some cases. For now, just don't move to incoming state in this case.
                 if (!BluetoothUuid.isAvrcpTarget(uuid)) {
                     mBluetoothService.notifyIncomingA2dpConnection(address);
+                } else {
+                    a2dp.allowIncomingConnect(device, authorized);
                 }
             } else {
                 Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address);
@@ -554,7 +564,7 @@
             Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
         }
         log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized);
-        return authorized;
+        if (!authorized) a2dp.allowIncomingConnect(device, authorized);
     }
 
     private boolean onAgentOutOfBandDataAvailable(String objectPath) {
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 6144766..643581e 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -27,8 +27,8 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothDeviceProfileState;
+import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfileState;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
@@ -67,6 +67,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
+import java.io.RandomAccessFile;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -103,7 +104,6 @@
     private static final int MESSAGE_REGISTER_SDP_RECORDS = 1;
     private static final int MESSAGE_FINISH_DISABLE = 2;
     private static final int MESSAGE_UUID_INTENT = 3;
-    private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4;
     private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 5;
 
     // The time (in millisecs) to delay the pairing attempt after the first
@@ -143,6 +143,11 @@
     private static String mDockAddress;
     private String mDockPin;
 
+    private static final String INCOMING_CONNECTION_FILE =
+      "/data/misc/bluetooth/incoming_connection.conf";
+    private HashMap<String, Pair<Integer, String>> mIncomingConnections;
+
+
     private static class RemoteService {
         public String address;
         public ParcelUuid uuid;
@@ -210,6 +215,7 @@
 
         filter.addAction(Intent.ACTION_DOCK_EVENT);
         mContext.registerReceiver(mReceiver, filter);
+        mIncomingConnections = new HashMap<String, Pair<Integer, String>>();
     }
 
     public static synchronized String readDockBluetoothAddress() {
@@ -501,15 +507,6 @@
                     makeServiceChannelCallbacks(address);
                 }
                 break;
-            case MESSAGE_DISCOVERABLE_TIMEOUT:
-                int mode = msg.arg1;
-                if (isEnabledInternal()) {
-                    // TODO: Switch back to the previous scan mode
-                    // This is ok for now, because we only use
-                    // CONNECTABLE and CONNECTABLE_DISCOVERABLE
-                    setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, -1);
-                }
-                break;
             case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
                 address = (String)msg.obj;
                 if (address != null) {
@@ -743,8 +740,6 @@
 
             if (state == BluetoothDevice.BOND_BONDED) {
                 addProfileState(address);
-            } else if (state == BluetoothDevice.BOND_NONE) {
-                removeProfileState(address);
             }
 
             if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
@@ -1078,21 +1073,17 @@
 
         switch (mode) {
         case BluetoothAdapter.SCAN_MODE_NONE:
-            mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
             pairable = false;
             discoverable = false;
             break;
         case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
-            mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
             pairable = true;
             discoverable = false;
             break;
         case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
-            mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
+            setDiscoverableTimeout(duration);
             pairable = true;
             discoverable = true;
-            Message msg = mHandler.obtainMessage(MESSAGE_DISCOVERABLE_TIMEOUT);
-            mHandler.sendMessageDelayed(msg, duration * 1000);
             if (DBG) Log.d(TAG, "BT Discoverable for " + duration + " seconds");
             break;
         default:
@@ -1326,6 +1317,8 @@
     }
 
     public synchronized boolean removeBondInternal(String address) {
+        // Unset the trusted device state and then unpair
+        setTrust(address, false);
         return removeDeviceNative(getObjectPathFromAddress(address));
     }
 
@@ -2175,10 +2168,6 @@
         return state;
     }
 
-    private void removeProfileState(String address) {
-        mDeviceProfileState.remove(address);
-    }
-
     private void initProfileState() {
         String []bonds = null;
         String val = getPropertyInternal("Devices");
@@ -2227,6 +2216,11 @@
         mA2dpService = a2dpService;
     }
 
+    /*package*/ Integer getAuthorizationAgentRequestData(String address) {
+        Integer data = mEventLoop.getAuthorizationAgentRequestData().remove(address);
+        return data;
+    }
+
     public void sendProfileStateMessage(int profile, int cmd) {
         Message msg = new Message();
         msg.what = cmd;
@@ -2237,6 +2231,116 @@
         }
     }
 
+    private void createIncomingConnectionStateFile() {
+        File f = new File(INCOMING_CONNECTION_FILE);
+        if (!f.exists()) {
+            try {
+                f.createNewFile();
+            } catch (IOException e) {
+                Log.e(TAG, "IOException: cannot create file");
+            }
+        }
+    }
+
+    /** @hide */
+    public Pair<Integer, String> getIncomingState(String address) {
+        if (mIncomingConnections.isEmpty()) {
+            createIncomingConnectionStateFile();
+            readIncomingConnectionState();
+        }
+        return mIncomingConnections.get(address);
+    }
+
+    private void readIncomingConnectionState() {
+        synchronized(mIncomingConnections) {
+            FileInputStream fstream = null;
+            try {
+              fstream = new FileInputStream(INCOMING_CONNECTION_FILE);
+              DataInputStream in = new DataInputStream(fstream);
+              BufferedReader file = new BufferedReader(new InputStreamReader(in));
+              String line;
+              while((line = file.readLine()) != null) {
+                  line = line.trim();
+                  if (line.length() == 0) continue;
+                  String[] value = line.split(",");
+                  if (value != null && value.length == 3) {
+                      Integer val1 = Integer.parseInt(value[1]);
+                      Pair<Integer, String> val = new Pair(val1, value[2]);
+                      mIncomingConnections.put(value[0], val);
+                  }
+              }
+            } catch (FileNotFoundException e) {
+                log("FileNotFoundException: readIncomingConnectionState" + e.toString());
+            } catch (IOException e) {
+                log("IOException: readIncomingConnectionState" + e.toString());
+            } finally {
+                if (fstream != null) {
+                    try {
+                        fstream.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+    }
+
+    private void truncateIncomingConnectionFile() {
+        RandomAccessFile r = null;
+        try {
+            r = new RandomAccessFile(INCOMING_CONNECTION_FILE, "rw");
+            r.setLength(0);
+        } catch (FileNotFoundException e) {
+            log("FileNotFoundException: truncateIncomingConnectionState" + e.toString());
+        } catch (IOException e) {
+            log("IOException: truncateIncomingConnectionState" + e.toString());
+        } finally {
+            if (r != null) {
+                try {
+                    r.close();
+                } catch (IOException e) {
+                    // ignore
+                 }
+            }
+        }
+    }
+
+    /** @hide */
+    public void writeIncomingConnectionState(String address, Pair<Integer, String> data) {
+        synchronized(mIncomingConnections) {
+            mIncomingConnections.put(address, data);
+
+            truncateIncomingConnectionFile();
+            BufferedWriter out = null;
+            StringBuilder value = new StringBuilder();
+            try {
+                out = new BufferedWriter(new FileWriter(INCOMING_CONNECTION_FILE, true));
+                for (String devAddress: mIncomingConnections.keySet()) {
+                  Pair<Integer, String> val = mIncomingConnections.get(devAddress);
+                  value.append(devAddress);
+                  value.append(",");
+                  value.append(val.first.toString());
+                  value.append(",");
+                  value.append(val.second);
+                  value.append("\n");
+                }
+                out.write(value.toString());
+            } catch (FileNotFoundException e) {
+                log("FileNotFoundException: writeIncomingConnectionState" + e.toString());
+            } catch (IOException e) {
+                log("IOException: writeIncomingConnectionState" + e.toString());
+            } finally {
+                if (out != null) {
+                    try {
+                        out.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+    }
+
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
@@ -2287,4 +2391,5 @@
             short channel);
     private native boolean removeServiceRecordNative(int handle);
     private native boolean setLinkTimeoutNative(String path, int num_slots);
+    native boolean setAuthorizationNative(String address, boolean value, int data);
 }
diff --git a/core/jni/android_nfc_NdefMessage.cpp b/core/jni/android_nfc_NdefMessage.cpp
index 9beef2a..aff8aa6 100644
--- a/core/jni/android_nfc_NdefMessage.cpp
+++ b/core/jni/android_nfc_NdefMessage.cpp
@@ -102,6 +102,19 @@
         }
         TRACE("phFriNfc_NdefRecord_Parse() returned 0x%04x", status);
 
+        // We don't exactly know what *is* a valid length, but a simple
+        // sanity check is to make sure that the length of the header
+        // plus all fields does not exceed raw_msg_size. The min length
+        // of the header is 3 bytes: TNF, Type Length, Payload Length
+        // (ID length field is optional!)
+        uint64_t indicatedMsgLength = 3 + record.TypeLength + record.IdLength +
+                (uint64_t)record.PayloadLength;
+        if (indicatedMsgLength >
+                (uint64_t)raw_msg_size) {
+            LOGE("phFri_NdefRecord_Parse: invalid length field");
+            goto end;
+        }
+
         type = e->NewByteArray(record.TypeLength);
         if (type == NULL) {
             LOGD("NFC_Set Record Type Error\n");
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index de003df..73dfdbe1 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -106,7 +106,7 @@
                                                          "(Ljava/lang/String;Z)V");
 
     method_onAgentAuthorize = env->GetMethodID(clazz, "onAgentAuthorize",
-                                               "(Ljava/lang/String;Ljava/lang/String;)Z");
+                                               "(Ljava/lang/String;Ljava/lang/String;I)V");
     method_onAgentOutOfBandDataAvailable = env->GetMethodID(clazz, "onAgentOutOfBandDataAvailable",
                                                "(Ljava/lang/String;)Z");
     method_onAgentCancel = env->GetMethodID(clazz, "onAgentCancel", "()V");
@@ -917,29 +917,11 @@
         LOGV("... object_path = %s", object_path);
         LOGV("... uuid = %s", uuid);
 
-        bool auth_granted =
-            env->CallBooleanMethod(nat->me, method_onAgentAuthorize,
-                env->NewStringUTF(object_path), env->NewStringUTF(uuid));
+        dbus_message_ref(msg);  // increment refcount because we pass to java
+        env->CallBooleanMethod(nat->me, method_onAgentAuthorize,
+                env->NewStringUTF(object_path), env->NewStringUTF(uuid),
+                int(msg));
 
-        // reply
-        if (auth_granted) {
-            DBusMessage *reply = dbus_message_new_method_return(msg);
-            if (!reply) {
-                LOGE("%s: Cannot create message reply\n", __FUNCTION__);
-                goto failure;
-            }
-            dbus_connection_send(nat->conn, reply, NULL);
-            dbus_message_unref(reply);
-        } else {
-            DBusMessage *reply = dbus_message_new_error(msg,
-                    "org.bluez.Error.Rejected", "Authorization rejected");
-            if (!reply) {
-                LOGE("%s: Cannot create message reply\n", __FUNCTION__);
-                goto failure;
-            }
-            dbus_connection_send(nat->conn, reply, NULL);
-            dbus_message_unref(reply);
-        }
         goto success;
     } else if (dbus_message_is_method_call(msg,
             "org.bluez.Agent", "OutOfBandAvailable")) {
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index daa59a6..be2e5f3 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -599,6 +599,35 @@
     return JNI_FALSE;
 }
 
+static jboolean setAuthorizationNative(JNIEnv *env, jobject object, jstring address,
+                         jboolean val, int nativeData) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        DBusMessage *msg = (DBusMessage *)nativeData;
+        DBusMessage *reply;
+        if (val) {
+            reply = dbus_message_new_method_return(msg);
+        } else {
+            reply = dbus_message_new_error(msg,
+                    "org.bluez.Error.Rejected", "Authorization rejected");
+        }
+        if (!reply) {
+            LOGE("%s: Cannot create message reply D-Bus\n", __FUNCTION__);
+            dbus_message_unref(msg);
+            return JNI_FALSE;
+        }
+
+        dbus_connection_send(nat->conn, reply, NULL);
+        dbus_message_unref(msg);
+        dbus_message_unref(reply);
+        return JNI_TRUE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
 static jboolean setPinNative(JNIEnv *env, jobject object, jstring address,
                          jstring pin, int nativeData) {
 #ifdef HAVE_BLUETOOTH
@@ -1029,6 +1058,7 @@
             (void *)setPairingConfirmationNative},
     {"setPasskeyNative", "(Ljava/lang/String;II)Z", (void *)setPasskeyNative},
     {"setRemoteOutOfBandDataNative", "(Ljava/lang/String;[B[BI)Z", (void *)setRemoteOutOfBandDataNative},
+    {"setAuthorizationNative", "(Ljava/lang/String;ZI)Z", (void *)setAuthorizationNative},
     {"setPinNative", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)setPinNative},
     {"cancelPairingUserInputNative", "(Ljava/lang/String;I)Z",
             (void *)cancelPairingUserInputNative},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c582eee..55cb6c8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -150,6 +150,15 @@
         android:label="@string/permlab_receiveMms"
         android:description="@string/permdesc_receiveMms" />
 
+    <!-- Allows an application to receive emergency cell broadcast messages,
+         to record or display them to the user. Reserved for system apps.
+         @hide Pending API council approval -->
+    <permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST"
+        android:permissionGroup="android.permission-group.MESSAGES"
+        android:protectionLevel="signatureOrSystem"
+        android:label="@string/permlab_receiveEmergencyBroadcast"
+        android:description="@string/permdesc_receiveEmergencyBroadcast" />
+
     <!-- Allows an application to read SMS messages. -->
     <permission android:name="android.permission.READ_SMS"
         android:permissionGroup="android.permission-group.MESSAGES"
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index fdc3679..91cc3ed 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -183,6 +183,8 @@
     <string name="permdesc_receiveSms" msgid="6298292335965966117">"Aplikaciji omogućuje primanje i obradu SMS poruka. Zlonamjerne aplikacije mogu pratiti vaše poruke ili ih izbrisati prije nego što ih vi vidite."</string>
     <string name="permlab_receiveMms" msgid="8894700916188083287">"primanje MMS-a"</string>
     <string name="permdesc_receiveMms" msgid="4563346832000174373">"Aplikaciji omogućuje primanje i obradu MMS poruka. Zlonamjerne aplikacije mogu pratiti vaše poruke ili ih izbrisati prije nego što ih vi vidite."</string>
+    <string name="permlab_receiveEmergencyBroadcast" msgid="1803477660846288089">"primanje hitnih odašiljanja"</string>
+    <string name="permdesc_receiveEmergencyBroadcast" msgid="7118393393716546131">"Omogućuje aplikaciji primanje i obradu poruka hitnih odašiljanja. Ta je dozvola dostupna samo aplikacijama sustava."</string>
     <string name="permlab_sendSms" msgid="5600830612147671529">"slanje SMS poruka"</string>
     <string name="permdesc_sendSms" msgid="1946540351763502120">"Aplikaciji omogućuje slanje SMS poruka. Zlonamjerne aplikacije mogu stvarati troškove slanjem poruka bez vaše potvrde."</string>
     <string name="permlab_readSms" msgid="4085333708122372256">"čitanje SMS-a ili MMS-a"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 916dd81..abc27e2 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -125,6 +125,7 @@
 
     <!-- Regex array of allowable upstream ifaces for tethering - for example if you want
          tethering on a new interface called "foo2" add <item>"foo\\d"</item> to the array -->
+    <!-- Interfaces will be prioritized according to the order listed -->
     <string-array translatable="false" name="config_tether_upstream_regexs">
     </string-array>
 
@@ -478,4 +479,15 @@
     <string name="config_wimaxServiceClassname"></string>
     <!-- Name of the wimax state tracker clas -->
     <string name="config_wimaxStateTrackerClassname"></string>
+
+    <!-- Set to true if the RSSI should always display CDMA signal strength
+         even on EVDO -->
+    <bool name="config_alwaysUseCdmaRssi">false</bool>
+
+    <!-- If this value is true, duplicate Source/Destination port fields
+         in WDP header of some carriers OMADM wap push are supported.
+         ex: MSGTYPE-TotalSegments-CurrentSegment
+             -SourcePortDestPort-SourcePortDestPort-OMADM PDU
+         If false, not supported. -->
+    <bool name="config_duplicate_port_omadm_wappush">false</bool>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ae3cd02..9a88ab3 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -427,6 +427,13 @@
       your messages or delete them without showing them to you.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_receiveEmergencyBroadcast">receive emergency broadcasts</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_receiveEmergencyBroadcast">Allows application to receive
+      and process emergency broadcast messages. This permission is only available
+      to system applications.</string>
+
+     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_sendSms">send SMS messages</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_sendSms">Allows application to send SMS
diff --git a/include/utils/RefBase.h b/include/utils/RefBase.h
index c24c0db..d92cfb0 100644
--- a/include/utils/RefBase.h
+++ b/include/utils/RefBase.h
@@ -112,10 +112,23 @@
         getWeakRefs()->trackMe(enable, retain); 
     }
 
+    // used to override the RefBase destruction.
+    class Destroyer {
+        friend class RefBase;
+    public:
+        virtual ~Destroyer();
+    private:
+        virtual void destroy(RefBase const* base) = 0;
+    };
+
+    // Make sure to never acquire a strong reference from this function. The
+    // same restrictions than for destructors apply.
+    void setDestroyer(Destroyer* destroyer);
+
 protected:
                             RefBase();
     virtual                 ~RefBase();
-    
+
     //! Flags for extendObjectLifetime()
     enum {
         OBJECT_LIFETIME_WEAK    = 0x0001,
diff --git a/libs/utils/RefBase.cpp b/libs/utils/RefBase.cpp
index d28b751..545da7d 100644
--- a/libs/utils/RefBase.cpp
+++ b/libs/utils/RefBase.cpp
@@ -48,6 +48,11 @@
 
 // ---------------------------------------------------------------------------
 
+RefBase::Destroyer::~Destroyer() {
+}
+
+// ---------------------------------------------------------------------------
+
 class RefBase::weakref_impl : public RefBase::weakref_type
 {
 public:
@@ -55,7 +60,7 @@
     volatile int32_t    mWeak;
     RefBase* const      mBase;
     volatile int32_t    mFlags;
-
+    Destroyer*          mDestroyer;
 
 #if !DEBUG_REFS
 
@@ -64,6 +69,7 @@
         , mWeak(0)
         , mBase(base)
         , mFlags(0)
+        , mDestroyer(0)
     {
     }
 
@@ -310,7 +316,11 @@
     if (c == 1) {
         const_cast<RefBase*>(this)->onLastStrongRef(id);
         if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
-            delete this;
+            if (refs->mDestroyer) {
+                refs->mDestroyer->destroy(this);
+            } else {
+                delete this;
+            }
         }
     }
     refs->removeWeakRef(id);
@@ -345,7 +355,9 @@
     return mRefs->mStrong;
 }
 
-
+void RefBase::setDestroyer(RefBase::Destroyer* destroyer) {
+    mRefs->mDestroyer = destroyer;
+}
 
 RefBase* RefBase::weakref_type::refBase() const
 {
@@ -369,16 +381,28 @@
     if (c != 1) return;
     
     if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) {
-        if (impl->mStrong == INITIAL_STRONG_VALUE)
-            delete impl->mBase;
-        else {
-//            LOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase);
+        if (impl->mStrong == INITIAL_STRONG_VALUE) {
+            if (impl->mBase) {
+                if (impl->mDestroyer) {
+                    impl->mDestroyer->destroy(impl->mBase);
+                } else {
+                    delete impl->mBase;
+                }
+            }
+        } else {
+            // LOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase);
             delete impl;
         }
     } else {
         impl->mBase->onLastWeakRef(id);
         if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) {
-            delete impl->mBase;
+            if (impl->mBase) {
+                if (impl->mDestroyer) {
+                    impl->mDestroyer->destroy(impl->mBase);
+                } else {
+                    delete impl->mBase;
+                }
+            }
         }
     }
 }
@@ -502,10 +526,10 @@
 
 RefBase::~RefBase()
 {
-//    LOGV("Destroying RefBase %p (refs %p)\n", this, mRefs);
-    if (mRefs->mWeak == 0) {
-//        LOGV("Freeing refs %p of old RefBase %p\n", mRefs, this);
-        delete mRefs;
+    if ((mRefs->mFlags & OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK) {
+        if (mRefs->mWeak == 0) {
+            delete mRefs;
+        }
     }
 }
 
diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp
index 4ad1c12..42664b2 100644
--- a/media/libstagefright/HTTPStream.cpp
+++ b/media/libstagefright/HTTPStream.cpp
@@ -124,6 +124,83 @@
     return result;
 }
 
+// Apparently under our linux closing a socket descriptor from one thread
+// will not unblock a pending send/recv on that socket on another thread.
+static ssize_t MySendReceive(
+        int s, void *data, size_t size, int flags, bool sendData) {
+    ssize_t result = 0;
+
+    if (s < 0) {
+        return -1;
+    }
+    while (size > 0) {
+        fd_set rs, ws, es;
+        FD_ZERO(&rs);
+        FD_ZERO(&ws);
+        FD_ZERO(&es);
+        FD_SET(s, sendData ? &ws : &rs);
+        FD_SET(s, &es);
+
+        struct timeval tv;
+        tv.tv_sec = 0;
+        tv.tv_usec = 100000ll;
+
+        int nfds = ::select(
+                s + 1,
+                sendData ? NULL : &rs,
+                sendData ? &ws : NULL,
+                &es,
+                &tv);
+
+        if (nfds < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+
+            result = -errno;
+            break;
+        } else if (nfds == 0) {
+            // timeout
+
+            continue;
+        }
+
+        CHECK_EQ(nfds, 1);
+
+        ssize_t nbytes =
+            sendData ? send(s, data, size, flags) : recv(s, data, size, flags);
+
+        if (nbytes < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+
+            result = -errno;
+            break;
+        } else if (nbytes == 0) {
+            result = 0;
+            break;
+        }
+
+        data = (uint8_t *)data + nbytes;
+        size -= nbytes;
+
+        result = nbytes;
+        break;
+    }
+
+    return result;
+}
+
+static ssize_t MySend(int s, const void *data, size_t size, int flags) {
+    return MySendReceive(
+            s, const_cast<void *>(data), size, flags, true /* sendData */);
+}
+
+static ssize_t MyReceive(int s, void *data, size_t size, int flags) {
+    return MySendReceive(s, data, size, flags, false /* sendData */);
+}
+
 status_t HTTPStream::connect(const char *server, int port) {
     Mutex::Autolock autoLock(mLock);
 
@@ -220,16 +297,12 @@
     }
 
     while (size > 0) {
-        ssize_t n = ::send(mSocket, data, size, 0);
+        ssize_t n = MySend(mSocket, data, size, 0);
 
         if (n < 0) {
-            if (errno == EINTR) {
-                continue;
-            }
-
             disconnect();
 
-            return ERROR_IO;
+            return n;
         } else if (n == 0) {
             disconnect();
 
@@ -265,12 +338,8 @@
 
     for (;;) {
         char c;
-        ssize_t n = recv(mSocket, &c, 1, 0);
+        ssize_t n = MyReceive(mSocket, &c, 1, 0);
         if (n < 0) {
-            if (errno == EINTR) {
-                continue;
-            }
-
             disconnect();
 
             return ERROR_IO;
@@ -383,14 +452,10 @@
 ssize_t HTTPStream::receive(void *data, size_t size) {
     size_t total = 0;
     while (total < size) {
-        ssize_t n = recv(mSocket, (char *)data + total, size - total, 0);
+        ssize_t n = MyReceive(mSocket, (char *)data + total, size - total, 0);
 
         if (n < 0) {
-            if (errno == EINTR) {
-                continue;
-            }
-
-            LOGE("recv failed, errno = %d (%s)", errno, strerror(errno));
+            LOGE("recv failed, errno = %d (%s)", (int)n, strerror(-n));
 
             disconnect();
             return (ssize_t)ERROR_IO;
diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
index 6001be9..e0c38b1 100644
--- a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
+++ b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
@@ -18,7 +18,6 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.nfc.ApduList;
 import android.nfc.INfcAdapterExtras;
 import android.nfc.NfcAdapter;
 import android.os.RemoteException;
@@ -208,17 +207,18 @@
         return sEmbeddedEe;
     }
 
-    public void registerTearDownApdus(String packageName, ApduList apdus) {
+    /**
+     * Authenticate the client application.
+     *
+     * Some implementations of NFC Adapter Extras may require applications
+     * to authenticate with a token, before using other methods.
+     *
+     * @param a implementation specific token
+     * @throws a {@link java.lang.SecurityException} if authentication failed
+     */
+    public void authenticate(byte[] token) {
         try {
-            sService.registerTearDownApdus(packageName, apdus);
-        } catch (RemoteException e) {
-            attemptDeadServiceRecovery(e);
-        }
-    }
-
-    public void unregisterTearDownApdus(String packageName) {
-        try {
-            sService.unregisterTearDownApdus(packageName);
+            sService.authenticate(token);
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
index 8c0fecf..b30a043 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
@@ -468,6 +468,9 @@
     ServiceState mServiceState;
     SignalStrength mSignalStrength;
 
+    // flag for signal strength behavior
+    private boolean mAlwaysUseCdmaRssi;
+
     // data connection
     private boolean mDataIconVisible;
     private boolean mHspaDataDistinguishable;
@@ -512,10 +515,10 @@
     private static final int sWimaxDisconnectedImg =
             R.drawable.stat_sys_data_wimax_signal_disconnected;
     private static final int sWimaxIdleImg = R.drawable.stat_sys_data_wimax_signal_idle;
-    private boolean mIsWimaxConnected = false;
     private boolean mIsWimaxEnabled = false;
     private int mWimaxSignal = 0;
     private int mWimaxState = 0;
+    private int mWimaxExtraState = 0;
 
     // state of inet connection - 0 not connected, 100 connected
     private int mInetCondition = 0;
@@ -600,6 +603,8 @@
         mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
         mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
         mService.setIcon("phone_signal", mPhoneSignalIconId, 0);
+        mAlwaysUseCdmaRssi = mContext.getResources().getBoolean(
+            com.android.internal.R.bool.config_alwaysUseCdmaRssi);
 
         // register for phone state notifications.
         ((TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE))
@@ -940,12 +945,6 @@
             break;
         case ConnectivityManager.TYPE_WIMAX:
             mInetCondition = inetCondition;
-            if (info.isConnected()) {
-                mIsWimaxConnected = true;
-                mService.setIconVisibility("wimax", true);
-            } else {
-                mIsWimaxConnected = false;
-            }
             updateWiMAX(intent);
             break;
         }
@@ -1084,7 +1083,8 @@
             // If 3G(EV) and 1x network are available than 3G should be
             // displayed, displayed RSSI should be from the EV side.
             // If a voice call is made then RSSI should switch to 1x.
-            if ((mPhoneState == TelephonyManager.CALL_STATE_IDLE) && isEvdo()){
+            if ((mPhoneState == TelephonyManager.CALL_STATE_IDLE) && isEvdo()
+                && !mAlwaysUseCdmaRssi) {
                 iconLevel = getEvdoLevel();
                 if (false) {
                     Slog.d(TAG, "use Evdo level=" + iconLevel + " to replace Cdma Level=" + getCdmaLevel());
@@ -1332,10 +1332,10 @@
         final String action = intent.getAction();
         int iconId = sWimaxDisconnectedImg;
 
-        if (action.equals(WimaxManagerConstants. WIMAX_ENABLED_STATUS_CHANGED)) {
-            int mWimaxStatus = intent.getIntExtra(WimaxManagerConstants.EXTRA_WIMAX_STATUS,
+        if (action.equals(WimaxManagerConstants.WIMAX_ENABLED_STATUS_CHANGED)) {
+            int wimaxStatus = intent.getIntExtra(WimaxManagerConstants.EXTRA_WIMAX_STATUS,
                     WimaxManagerConstants.WIMAX_STATUS_DISABLED);
-            switch(mWimaxStatus) {
+            switch(wimaxStatus) {
                 case WimaxManagerConstants.WIMAX_STATUS_ENABLED:
                     mIsWimaxEnabled = true;
                     break;
@@ -1343,31 +1343,29 @@
                     mIsWimaxEnabled = false;
                     break;
             }
+            mService.setIconVisibility("wimax", mIsWimaxEnabled);
         } else if (action.equals(WimaxManagerConstants.SIGNAL_LEVEL_CHANGED_ACTION)) {
             mWimaxSignal = intent.getIntExtra(WimaxManagerConstants.EXTRA_NEW_SIGNAL_LEVEL, 0);
         } else if (action.equals(WimaxManagerConstants.WIMAX_STATE_CHANGED_ACTION)) {
             mWimaxState = intent.getIntExtra(WimaxManagerConstants.EXTRA_WIMAX_STATE,
                     WimaxManagerConstants.WIMAX_STATE_UNKNOWN);
-            int mExtraWimaxState = intent.getIntExtra(
+            mWimaxExtraState = intent.getIntExtra(
                     WimaxManagerConstants.EXTRA_WIMAX_STATE_DETAIL,
                     WimaxManagerConstants.WIMAX_DEREGISTRATION);
-
-            switch(mWimaxState) {
-                case WimaxManagerConstants.WIMAX_STATE_DISCONNECTED:
-                    iconId = sWimaxDisconnectedImg;
-                    break;
-                case WimaxManagerConstants.WIMAX_STATE_CONNECTED:
-                    if(mExtraWimaxState == WimaxManagerConstants.WIMAX_IDLE) {
-                        iconId = sWimaxIdleImg;
-                    }
-                    else {
-                        iconId = sWimaxSignalImages[mInetCondition][mWimaxSignal];
-                    }
-                    break;
-            }
-            mService.setIcon("wimax", iconId, 0);
         }
-        mService.setIconVisibility("wimax", mIsWimaxEnabled);
+        switch(mWimaxState) {
+            case WimaxManagerConstants.WIMAX_STATE_DISCONNECTED:
+                iconId = sWimaxDisconnectedImg;
+                break;
+            case WimaxManagerConstants.WIMAX_STATE_CONNECTED:
+                if(mWimaxExtraState == WimaxManagerConstants.WIMAX_IDLE) {
+                    iconId = sWimaxIdleImg;
+                } else {
+                    iconId = sWimaxSignalImages[mInetCondition][mWimaxSignal];
+                }
+                break;
+        }
+        if (mIsWimaxEnabled) mService.setIcon("wimax", iconId, 0);
     }
 
     private final void updateGps(Intent intent) {
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 19abdc3..29b9ee4 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -55,6 +55,8 @@
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageStats;
+import android.content.pm.ParceledListSlice;
+
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -75,6 +77,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.Environment;
 import android.os.FileObserver;
@@ -2323,63 +2326,110 @@
         }
     }
 
-    public List<PackageInfo> getInstalledPackages(int flags) {
-        ArrayList<PackageInfo> finalList = new ArrayList<PackageInfo>();
-
-        synchronized (mPackages) {
-            if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) {
-                Iterator<PackageSetting> i = mSettings.mPackages.values().iterator();
-                while (i.hasNext()) {
-                    final PackageSetting ps = i.next();
-                    PackageInfo psPkg = generatePackageInfoFromSettingsLP(ps.name, flags);
-                    if(psPkg != null) {
-                        finalList.add(psPkg);
-                    }
-                }
-            }
-            else {
-                Iterator<PackageParser.Package> i = mPackages.values().iterator();
-                while (i.hasNext()) {
-                    final PackageParser.Package p = i.next();
-                    if (p.applicationInfo != null) {
-                        PackageInfo pi = generatePackageInfo(p, flags);
-                        if(pi != null) {
-                            finalList.add(pi);
-                        }
-                    }
-                }
+    private static final int getContinuationPoint(final String[] keys, final String key) {
+        final int index;
+        if (key == null) {
+            index = 0;
+        } else {
+            final int insertPoint = Arrays.binarySearch(keys, key);
+            if (insertPoint < 0) {
+                index = -insertPoint;
+            } else {
+                index = insertPoint + 1;
             }
         }
-        return finalList;
+        return index;
     }
 
-    public List<ApplicationInfo> getInstalledApplications(int flags) {
-        ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>();
-        synchronized(mPackages) {
-            if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) {
-                Iterator<PackageSetting> i = mSettings.mPackages.values().iterator();
-                while (i.hasNext()) {
-                    final PackageSetting ps = i.next();
-                    ApplicationInfo ai = generateApplicationInfoFromSettingsLP(ps.name, flags);
-                    if(ai != null) {
-                        finalList.add(ai);
+    public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, String lastRead) {
+        final ParceledListSlice<PackageInfo> list = new ParceledListSlice<PackageInfo>();
+        final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
+        final String[] keys;
+
+        synchronized (mPackages) {
+            if (listUninstalled) {
+                keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]);
+            } else {
+                keys = mPackages.keySet().toArray(new String[mPackages.size()]);
+            }
+
+            Arrays.sort(keys);
+            int i = getContinuationPoint(keys, lastRead);
+            final int N = keys.length;
+
+            while (i < N) {
+                final String packageName = keys[i++];
+
+                PackageInfo pi = null;
+                if (listUninstalled) {
+                    final PackageSetting ps = mSettings.mPackages.get(packageName);
+                    if (ps != null) {
+                        pi = generatePackageInfoFromSettingsLP(ps.name, flags);
                     }
+                } else {
+                    final PackageParser.Package p = mPackages.get(packageName);
+                    if (p != null) {
+                        pi = generatePackageInfo(p, flags);
+                    }
+                }
+
+                if (pi != null && !list.append(pi)) {
+                    break;
                 }
             }
-            else {
-                Iterator<PackageParser.Package> i = mPackages.values().iterator();
-                while (i.hasNext()) {
-                    final PackageParser.Package p = i.next();
-                    if (p.applicationInfo != null) {
-                        ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags);
-                        if(ai != null) {
-                            finalList.add(ai);
-                        }
-                    }
-                }
+
+            if (i == N) {
+                list.setLastSlice(true);
             }
         }
-        return finalList;
+
+        return list;
+    }
+
+    public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags,
+            String lastRead) {
+        final ParceledListSlice<ApplicationInfo> list = new ParceledListSlice<ApplicationInfo>();
+        final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
+        final String[] keys;
+
+        synchronized (mPackages) {
+            if (listUninstalled) {
+                keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]);
+            } else {
+                keys = mPackages.keySet().toArray(new String[mPackages.size()]);
+            }
+
+            Arrays.sort(keys);
+            int i = getContinuationPoint(keys, lastRead);
+            final int N = keys.length;
+
+            while (i < N) {
+                final String packageName = keys[i++];
+
+                ApplicationInfo ai = null;
+                if (listUninstalled) {
+                    final PackageSetting ps = mSettings.mPackages.get(packageName);
+                    if (ps != null) {
+                        ai = generateApplicationInfoFromSettingsLP(ps.name, flags);
+                    }
+                } else {
+                    final PackageParser.Package p = mPackages.get(packageName);
+                    if (p != null) {
+                        ai = PackageParser.generateApplicationInfo(p, flags);
+                    }
+                }
+
+                if (ai != null && !list.append(ai)) {
+                    break;
+                }
+            }
+
+            if (i == N) {
+                list.setLastSlice(true);
+            }
+        }
+
+        return list;
     }
 
     public List<ApplicationInfo> getPersistentApplications(int flags) {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 5aa0111..647f115 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -53,6 +53,7 @@
 import android.net.DhcpInfo;
 import android.net.NetworkUtils;
 import android.os.Binder;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -68,6 +69,17 @@
 import android.util.Slog;
 import android.text.TextUtils;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashMap;
@@ -76,9 +88,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.net.UnknownHostException;
+import java.util.UUID;
 
 import com.android.internal.app.IBatteryStats;
 import android.app.backup.IBackupManager;
@@ -176,6 +186,8 @@
     private static final int MESSAGE_START_SCAN         = 10;
     private static final int MESSAGE_REPORT_WORKSOURCE  = 11;
     private static final int MESSAGE_ENABLE_RSSI_POLLING = 12;
+    private static final int MESSAGE_WRITE_WIFI_AP_CONFIG = 13;
+    private static final int MESSAGE_READ_WIFI_AP_CONFIG = 14;
 
 
     private final  WifiHandler mWifiHandler;
@@ -218,6 +230,13 @@
     private static final String ACTION_DEVICE_IDLE =
             "com.android.server.WifiManager.action.DEVICE_IDLE";
 
+    private static final String WIFIAP_CONFIG_FILE = Environment.getDataDirectory() +
+            "/misc/wifi/softap.conf";
+
+    private static final int WIFIAP_CONFIG_VERSION = 1;
+    private WifiConfiguration mWifiApConfig = new WifiConfiguration();
+    private final Object mWifiApConfigLock = new Object();
+
     WifiService(Context context, WifiStateTracker tracker) {
         mContext = context;
         mWifiStateTracker = tracker;
@@ -281,6 +300,9 @@
 
                 }
             },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+
+        //Initiate a read of Wifi Ap configuration
+        Message.obtain(mWifiHandler, MESSAGE_READ_WIFI_AP_CONFIG).sendToTarget();
     }
 
     /**
@@ -634,38 +656,103 @@
 
     public WifiConfiguration getWifiApConfiguration() {
         enforceAccessPermission();
-        final ContentResolver cr = mContext.getContentResolver();
-        WifiConfiguration wifiConfig = new WifiConfiguration();
-        int authType;
-        try {
-            wifiConfig.SSID = Settings.Secure.getString(cr, Settings.Secure.WIFI_AP_SSID);
-            if (wifiConfig.SSID == null)
-                return null;
-            authType = Settings.Secure.getInt(cr, Settings.Secure.WIFI_AP_SECURITY);
-            wifiConfig.allowedKeyManagement.set(authType);
-            wifiConfig.preSharedKey = Settings.Secure.getString(cr, Settings.Secure.WIFI_AP_PASSWD);
-            return wifiConfig;
-        } catch (Settings.SettingNotFoundException e) {
-            Slog.e(TAG,"AP settings not found, returning");
-            return null;
+        synchronized (mWifiApConfigLock) {
+            WifiConfiguration config = new WifiConfiguration();
+            config.SSID = mWifiApConfig.SSID;
+            if (mWifiApConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+                config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+                config.preSharedKey = mWifiApConfig.preSharedKey;
+            } else {
+                config.allowedKeyManagement.set(KeyMgmt.NONE);
+            }
+            return config;
         }
     }
 
     public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
         enforceChangePermission();
-        final ContentResolver cr = mContext.getContentResolver();
-        boolean isWpa;
         if (wifiConfig == null)
             return;
-        Settings.Secure.putString(cr, Settings.Secure.WIFI_AP_SSID, wifiConfig.SSID);
-        isWpa = wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK);
-        Settings.Secure.putInt(cr,
-                               Settings.Secure.WIFI_AP_SECURITY,
-                               isWpa ? KeyMgmt.WPA_PSK : KeyMgmt.NONE);
-        if (isWpa)
-            Settings.Secure.putString(cr, Settings.Secure.WIFI_AP_PASSWD, wifiConfig.preSharedKey);
+        Message.obtain(mWifiHandler, MESSAGE_WRITE_WIFI_AP_CONFIG, wifiConfig).sendToTarget();
     }
 
+    /* Generate a default WPA2 based configuration with a random password.
+
+       We are changing the Wifi Ap configuration storage from secure settings to a
+       flat file accessible only by the system. A WPA2 based default configuration
+       will keep the device secure after the update */
+    private void setDefaultWifiApConfiguration() {
+        synchronized (mWifiApConfigLock) {
+            mWifiApConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
+            mWifiApConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+            String randomUUID = UUID.randomUUID().toString();
+            //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+            mWifiApConfig.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9,13);
+        }
+    }
+
+    private void writeWifiApConfigBlocked(WifiConfiguration wifiConfig) {
+        DataOutputStream out = null;
+        try {
+            out = new DataOutputStream(new BufferedOutputStream(
+                        new FileOutputStream(WIFIAP_CONFIG_FILE)));
+
+            out.writeInt(WIFIAP_CONFIG_VERSION);
+            out.writeUTF(wifiConfig.SSID);
+            if(wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+                out.writeInt(KeyMgmt.WPA_PSK);
+                out.writeUTF(wifiConfig.preSharedKey);
+            } else {
+                out.writeInt(KeyMgmt.NONE);
+            }
+            synchronized (mWifiApConfigLock) {
+                mWifiApConfig = wifiConfig;
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Error writing hotspot configuration" + e);
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {}
+            }
+        }
+    }
+
+    private void readWifiApConfigBlocked() {
+        DataInputStream in = null;
+        try {
+            WifiConfiguration config = new WifiConfiguration();
+            in = new DataInputStream(new BufferedInputStream(new FileInputStream(
+                            WIFIAP_CONFIG_FILE)));
+
+            int version = in.readInt();
+            if (version != 1) {
+                Slog.e(TAG, "Bad version on hotspot configuration file, set defaults");
+                setDefaultWifiApConfiguration();
+                return;
+            }
+            config.SSID = in.readUTF();
+            int authType = in.readInt();
+            config.allowedKeyManagement.set(authType);
+            if (authType != KeyMgmt.NONE) {
+                config.preSharedKey = in.readUTF();
+            }
+            synchronized (mWifiApConfigLock) {
+                mWifiApConfig = config;
+            }
+        } catch (IOException ignore) {
+            setDefaultWifiApConfiguration();
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {}
+            }
+        }
+    }
+
+
     /**
      * Enables/disables Wi-Fi AP synchronously. The driver is loaded
      * and soft access point configured as a single operation.
@@ -716,11 +803,7 @@
         if (enable) {
 
             /* Use default config if there is no existing config */
-            if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) {
-                wifiConfig = new WifiConfiguration();
-                wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
-                wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE);
-            }
+            if (wifiConfig == null) wifiConfig = getWifiApConfiguration();
 
             if (!mWifiStateTracker.loadDriver()) {
                 Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode");
@@ -2038,6 +2121,12 @@
                 case MESSAGE_ENABLE_RSSI_POLLING:
                     mWifiStateTracker.enableRssiPolling(msg.arg1 == 1);
                     break;
+                case MESSAGE_WRITE_WIFI_AP_CONFIG:
+                    writeWifiApConfigBlocked((WifiConfiguration) msg.obj);
+                    break;
+                case MESSAGE_READ_WIFI_AP_CONFIG:
+                    readWifiApConfigBlocked();
+                    break;
             }
         }
     }
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 1ec9b51..a430128 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -1170,8 +1170,8 @@
                     return null;
                 }
 
-                for (String iface : ifaces) {
-                    for (String regex : mUpstreamIfaceRegexs) {
+                for (String regex : mUpstreamIfaceRegexs) {
+                    for (String iface : ifaces) {
                         if (iface.matches(regex)) {
                             // verify it is active
                             InterfaceConfiguration ifcg = null;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index dfee803..da06f61 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -60,6 +60,7 @@
         mWidth(0), mHeight(0), mNeedsScaling(false), mFixedSize(false),
         mBypassState(false)
 {
+    setDestroyer(this);
 }
 
 Layer::~Layer()
@@ -76,6 +77,10 @@
     }
 }
 
+void Layer::destroy(RefBase const* base) {
+    mFlinger->destroyLayer(static_cast<LayerBase const*>(base));
+}
+
 status_t Layer::setToken(const sp<UserClient>& userClient,
         SharedClient* sharedClient, int32_t token)
 {
@@ -123,22 +128,6 @@
     return mSurface;
 }
 
-status_t Layer::ditch()
-{
-    // NOTE: Called from the main UI thread
-
-    // the layer is not on screen anymore. free as much resources as possible
-    mFreezeLock.clear();
-
-    EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay());
-    mBufferManager.destroy(dpy);
-    mSurface.clear();
-
-    Mutex::Autolock _l(mLock);
-    mWidth = mHeight = 0;
-    return NO_ERROR;
-}
-
 status_t Layer::setBuffers( uint32_t w, uint32_t h,
                             PixelFormat format, uint32_t flags)
 {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 7bb207a..2c4f756 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -44,7 +44,7 @@
 
 // ---------------------------------------------------------------------------
 
-class Layer : public LayerBaseClient
+class Layer : public LayerBaseClient, private RefBase::Destroyer
 {
 public:
             Layer(SurfaceFlinger* flinger, DisplayID display,
@@ -78,7 +78,6 @@
     virtual bool needsFiltering() const;
     virtual bool isSecure() const           { return mSecure; }
     virtual sp<Surface> createSurface() const;
-    virtual status_t ditch();
     virtual void onRemoved();
     virtual bool setBypass(bool enable);
 
@@ -95,6 +94,7 @@
         return mFreezeLock; }
 
 protected:
+    virtual void destroy(RefBase const* base);
     virtual void dump(String8& result, char* scratch, size_t size) const;
 
 private:
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 21c36e18..3986fde 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -583,10 +583,7 @@
      */
 
     // destroy client resources
-    sp<LayerBaseClient> layer = getOwner();
-    if (layer != 0) {
-        mFlinger->destroySurface(layer);
-    }
+    mFlinger->destroySurface(mOwner);
 }
 
 sp<LayerBaseClient> LayerBaseClient::Surface::getOwner() const {
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index afc5ec8..e69cb6a 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -196,10 +196,6 @@
      */
     virtual bool isSecure() const       { return false; }
 
-    /** Called from the main thread, when the surface is removed from the
-     * draw list */
-    virtual status_t ditch() { return NO_ERROR; }
-
     /** called with the state lock when the surface is removed from the
      *  current list */
     virtual void onRemoved() { };
@@ -264,7 +260,8 @@
     volatile    int32_t         mInvalidate;
                 
 
-protected:
+public:
+    // called from class SurfaceFlinger
     virtual ~LayerBase();
 
 private:
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index c08e2c9..a93d756 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -358,6 +358,9 @@
 {
     waitForEvent();
 
+    // call Layer's destructor
+    handleDestroyLayers();
+
     // check for transactions
     if (UNLIKELY(mConsoleSignals)) {
         handleConsoleEvents();
@@ -366,7 +369,7 @@
     if (LIKELY(mTransactionCount == 0)) {
         // if we're in a global transaction, don't do anything.
         const uint32_t mask = eTransactionNeeded | eTraversalNeeded;
-        uint32_t transactionFlags = getTransactionFlags(mask);
+        uint32_t transactionFlags = peekTransactionFlags(mask);
         if (LIKELY(transactionFlags)) {
             handleTransaction(transactionFlags);
         }
@@ -467,37 +470,26 @@
 
 void SurfaceFlinger::handleTransaction(uint32_t transactionFlags)
 {
-    Vector< sp<LayerBase> > ditchedLayers;
+    Mutex::Autolock _l(mStateLock);
+    const nsecs_t now = systemTime();
+    mDebugInTransaction = now;
 
-    /*
-     * Perform and commit the transaction
-     */
+    // Here we're guaranteed that some transaction flags are set
+    // so we can call handleTransactionLocked() unconditionally.
+    // We call getTransactionFlags(), which will also clear the flags,
+    // with mStateLock held to guarantee that mCurrentState won't change
+    // until the transaction is committed.
 
-    { // scope for the lock
-        Mutex::Autolock _l(mStateLock);
-        const nsecs_t now = systemTime();
-        mDebugInTransaction = now;
-        handleTransactionLocked(transactionFlags, ditchedLayers);
-        mLastTransactionTime = systemTime() - now;
-        mDebugInTransaction = 0;
-        // here the transaction has been committed
-    }
+    const uint32_t mask = eTransactionNeeded | eTraversalNeeded;
+    transactionFlags = getTransactionFlags(mask);
+    handleTransactionLocked(transactionFlags);
 
-    /*
-     * Clean-up all layers that went away
-     * (do this without the lock held)
-     */
-    const size_t count = ditchedLayers.size();
-    for (size_t i=0 ; i<count ; i++) {
-        if (ditchedLayers[i] != 0) {
-            //LOGD("ditching layer %p", ditchedLayers[i].get());
-            ditchedLayers[i]->ditch();
-        }
-    }
+    mLastTransactionTime = systemTime() - now;
+    mDebugInTransaction = 0;
+    // here the transaction has been committed
 }
 
-void SurfaceFlinger::handleTransactionLocked(
-        uint32_t transactionFlags, Vector< sp<LayerBase> >& ditchedLayers)
+void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags)
 {
     const LayerVector& currentLayers(mCurrentState.layersSortedByZ);
     const size_t count = currentLayers.size();
@@ -569,7 +561,6 @@
                 const sp<LayerBase>& layer(previousLayers[i]);
                 if (currentLayers.indexOf( layer ) < 0) {
                     // this layer is not visible anymore
-                    ditchedLayers.add(layer);
                     mDirtyRegionRemovedLayer.orSelf(layer->visibleRegionScreen);
                 }
             }
@@ -579,6 +570,31 @@
     commitTransaction();
 }
 
+void SurfaceFlinger::destroyLayer(LayerBase const* layer)
+{
+    Mutex::Autolock _l(mDestroyedLayerLock);
+    mDestroyedLayers.add(layer);
+    signalEvent();
+}
+
+void SurfaceFlinger::handleDestroyLayers()
+{
+    Vector<LayerBase const *> destroyedLayers;
+
+    { // scope for the lock
+        Mutex::Autolock _l(mDestroyedLayerLock);
+        destroyedLayers = mDestroyedLayers;
+        mDestroyedLayers.clear();
+    }
+
+    // call destructors without a lock held
+    const size_t count = destroyedLayers.size();
+    for (size_t i=0 ; i<count ; i++) {
+        //LOGD("destroying %s", destroyedLayers[i]->getName().string());
+        delete destroyedLayers[i];
+    }
+}
+
 sp<FreezeLock> SurfaceFlinger::getFreezeLock() const
 {
     return new FreezeLock(const_cast<SurfaceFlinger *>(this));
@@ -1030,15 +1046,15 @@
 ssize_t SurfaceFlinger::addClientLayer(const sp<Client>& client,
         const sp<LayerBaseClient>& lbc)
 {
-    Mutex::Autolock _l(mStateLock);
-
     // attach this layer to the client
-    ssize_t name = client->attachLayer(lbc);
+    size_t name = client->attachLayer(lbc);
+
+    Mutex::Autolock _l(mStateLock);
 
     // add this layer to the current state list
     addLayer_l(lbc);
 
-    return name;
+    return ssize_t(name);
 }
 
 status_t SurfaceFlinger::removeLayer(const sp<LayerBase>& layer)
@@ -1085,6 +1101,11 @@
     return NO_ERROR;
 }
 
+uint32_t SurfaceFlinger::peekTransactionFlags(uint32_t flags)
+{
+    return android_atomic_release_load(&mTransactionFlags);
+}
+
 uint32_t SurfaceFlinger::getTransactionFlags(uint32_t flags)
 {
     return android_atomic_and(~flags, &mTransactionFlags) & flags;
@@ -1314,38 +1335,18 @@
     return err;
 }
 
-status_t SurfaceFlinger::destroySurface(const sp<LayerBaseClient>& layer)
+status_t SurfaceFlinger::destroySurface(const wp<LayerBaseClient>& layer)
 {
     // called by ~ISurface() when all references are gone
-    
-    class MessageDestroySurface : public MessageBase {
-        SurfaceFlinger* flinger;
-        sp<LayerBaseClient> layer;
-    public:
-        MessageDestroySurface(
-                SurfaceFlinger* flinger, const sp<LayerBaseClient>& layer)
-            : flinger(flinger), layer(layer) { }
-        virtual bool handler() {
-            sp<LayerBaseClient> l(layer);
-            layer.clear(); // clear it outside of the lock;
-            Mutex::Autolock _l(flinger->mStateLock);
-            /*
-             * remove the layer from the current list -- chances are that it's 
-             * not in the list anyway, because it should have been removed 
-             * already upon request of the client (eg: window manager). 
-             * However, a buggy client could have not done that.
-             * Since we know we don't have any more clients, we don't need
-             * to use the purgatory.
-             */
-            status_t err = flinger->removeLayer_l(l);
-            LOGE_IF(err<0 && err != NAME_NOT_FOUND,
-                    "error removing layer=%p (%s)", l.get(), strerror(-err));
-            return true;
-        }
-    };
-
-    postMessageAsync( new MessageDestroySurface(this, layer) );
-    return NO_ERROR;
+    status_t err = NO_ERROR;
+    sp<LayerBaseClient> l(layer.promote());
+    if (l != NULL) {
+        Mutex::Autolock _l(mStateLock);
+        err = removeLayer_l(l);
+        LOGE_IF(err<0 && err != NAME_NOT_FOUND,
+                "error removing layer=%p (%s)", l.get(), strerror(-err));
+    }
+    return err;
 }
 
 status_t SurfaceFlinger::setClientState(
@@ -2253,15 +2254,17 @@
     return NO_ERROR;
 }
 
-ssize_t Client::attachLayer(const sp<LayerBaseClient>& layer)
+size_t Client::attachLayer(const sp<LayerBaseClient>& layer)
 {
-    int32_t name = android_atomic_inc(&mNameGenerator);
+    Mutex::Autolock _l(mLock);
+    size_t name = mNameGenerator++;
     mLayers.add(name, layer);
     return name;
 }
 
 void Client::detachLayer(const LayerBaseClient* layer)
 {
+    Mutex::Autolock _l(mLock);
     // we do a linear search here, because this doesn't happen often
     const size_t count = mLayers.size();
     for (size_t i=0 ; i<count ; i++) {
@@ -2271,9 +2274,11 @@
         }
     }
 }
-sp<LayerBaseClient> Client::getLayerUser(int32_t i) const {
+sp<LayerBaseClient> Client::getLayerUser(int32_t i) const
+{
+    Mutex::Autolock _l(mLock);
     sp<LayerBaseClient> lbc;
-    const wp<LayerBaseClient>& layer(mLayers.valueFor(i));
+    wp<LayerBaseClient> layer(mLayers.valueFor(i));
     if (layer != 0) {
         lbc = layer.promote();
         LOGE_IF(lbc==0, "getLayerUser(name=%d) is dead", int(i));
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index df1ca48..9fa98cf 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -66,7 +66,7 @@
     status_t initCheck() const;
 
     // protected by SurfaceFlinger::mStateLock
-    ssize_t attachLayer(const sp<LayerBaseClient>& layer);
+    size_t attachLayer(const sp<LayerBaseClient>& layer);
     void detachLayer(const LayerBaseClient* layer);
     sp<LayerBaseClient> getLayerUser(int32_t i) const;
 
@@ -82,9 +82,15 @@
     virtual status_t destroySurface(SurfaceID surfaceId);
     virtual status_t setState(int32_t count, const layer_state_t* states);
 
-    DefaultKeyedVector< size_t, wp<LayerBaseClient> > mLayers;
+    // constant
     sp<SurfaceFlinger> mFlinger;
-    int32_t mNameGenerator;
+
+    // protected by mLock
+    DefaultKeyedVector< size_t, wp<LayerBaseClient> > mLayers;
+    size_t mNameGenerator;
+
+    // thread-safe
+    mutable Mutex mLock;
 };
 
 class UserClient : public BnSurfaceComposerClient
@@ -212,6 +218,7 @@
     status_t removeLayer(const sp<LayerBase>& layer);
     status_t addLayer(const sp<LayerBase>& layer);
     status_t invalidateLayerVisibility(const sp<LayerBase>& layer);
+    void destroyLayer(LayerBase const* layer);
 
     sp<Layer> getLayer(const sp<ISurface>& sur) const;
 
@@ -249,7 +256,7 @@
             uint32_t w, uint32_t h, uint32_t flags);
 
     status_t removeSurface(const sp<Client>& client, SurfaceID sid);
-    status_t destroySurface(const sp<LayerBaseClient>& layer);
+    status_t destroySurface(const wp<LayerBaseClient>& layer);
     status_t setClientState(const sp<Client>& client,
             int32_t count, const layer_state_t* states);
 
@@ -294,9 +301,8 @@
 private:
             void        handleConsoleEvents();
             void        handleTransaction(uint32_t transactionFlags);
-            void        handleTransactionLocked(
-                            uint32_t transactionFlags, 
-                            Vector< sp<LayerBase> >& ditchedLayers);
+            void        handleTransactionLocked(uint32_t transactionFlags);
+            void        handleDestroyLayers();
 
             void        computeVisibleRegions(
                             LayerVector& currentLayers,
@@ -319,6 +325,7 @@
             status_t    purgatorizeLayer_l(const sp<LayerBase>& layer);
 
             uint32_t    getTransactionFlags(uint32_t flags);
+            uint32_t    peekTransactionFlags(uint32_t flags);
             uint32_t    setTransactionFlags(uint32_t flags);
             void        commitTransaction();
 
@@ -415,6 +422,11 @@
                 // these are thread safe
     mutable     Barrier                     mReadyToRunBarrier;
 
+
+                // protected by mDestroyedLayerLock;
+    mutable     Mutex                       mDestroyedLayerLock;
+                Vector<LayerBase const *>   mDestroyedLayers;
+
                 // atomic variables
                 enum {
                     eConsoleReleased = 1,
diff --git a/telephony/java/android/telephony/SmsCbConstants.java b/telephony/java/android/telephony/SmsCbConstants.java
new file mode 100644
index 0000000..a1b4adf
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbConstants.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011 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.telephony;
+
+/**
+ * Constants used in SMS Cell Broadcast messages.
+ *
+ * {@hide}
+ */
+public interface SmsCbConstants {
+    /** Start of PWS Message Identifier range (includes ETWS and CMAS). */
+    public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100;
+
+    /** Bitmask for messages of ETWS type (including future extensions). */
+    public static final int MESSAGE_ID_ETWS_TYPE_MASK       = 0xFFF8;
+
+    /** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */
+    public static final int MESSAGE_ID_ETWS_TYPE            = 0x1100;
+
+    /** ETWS Message Identifier for earthquake warning message. */
+    public static final int MESSAGE_ID_ETWS_EARTHQUAKE_WARNING      = 0x1100;
+
+    /** ETWS Message Identifier for tsunami warning message. */
+    public static final int MESSAGE_ID_ETWS_TSUNAMI_WARNING         = 0x1101;
+
+    /** ETWS Message Identifier for earthquake and tsunami combined warning message. */
+    public static final int MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING  = 0x1102;
+
+    /** ETWS Message Identifier for test message. */
+    public static final int MESSAGE_ID_ETWS_TEST_MESSAGE            = 0x1103;
+
+    /** ETWS Message Identifier for messages related to other emergency types. */
+    public static final int MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE    = 0x1104;
+
+    /** Start of CMAS Message Identifier range. */
+    public static final int MESSAGE_ID_CMAS_FIRST_IDENTIFIER                = 0x1112;
+
+    /** CMAS Message Identifier for Presidential Level alerts. */
+    public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL        = 0x1112;
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED = 0x1113;
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY  = 0x1114;
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED = 0x1115;
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY   = 0x1116;
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED = 0x1117;
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY   = 0x1118;
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED  = 0x1119;
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY    = 0x111A;
+
+    /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert). */
+    public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY = 0x111B;
+
+    /** CMAS Message Identifier for the Required Monthly Test. */
+    public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST     = 0x111C;
+
+    /** CMAS Message Identifier for CMAS Exercise. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE                  = 0x111D;
+
+    /** CMAS Message Identifier for operator defined use. */
+    public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE      = 0x111E;
+
+    /** End of CMAS Message Identifier range (including future extensions). */
+    public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER                 = 0x112F;
+
+    /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
+    public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER                  = 0x18FF;
+
+    /** ETWS message code flag to activate the popup display. */
+    public static final int MESSAGE_CODE_ETWS_ACTIVATE_POPUP                = 0x100;
+
+    /** ETWS message code flag to activate the emergency user alert. */
+    public static final int MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT          = 0x200;
+
+    /** ETWS warning type value for earthquake. */
+    public static final int ETWS_WARNING_TYPE_EARTHQUAKE                    = 0x00;
+
+    /** ETWS warning type value for tsunami. */
+    public static final int ETWS_WARNING_TYPE_TSUNAMI                       = 0x01;
+
+    /** ETWS warning type value for earthquake and tsunami. */
+    public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI        = 0x02;
+
+    /** ETWS warning type value for test broadcast. */
+    public static final int ETWS_WARNING_TYPE_TEST                          = 0x03;
+
+    /** ETWS warning type value for other notifications. */
+    public static final int ETWS_WARNING_TYPE_OTHER                         = 0x04;
+}
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
index 5608402..383e0f9 100644
--- a/telephony/java/android/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -16,9 +16,11 @@
 
 package android.telephony;
 
+import android.text.format.Time;
 import android.util.Log;
 
 import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.gsm.SmsCbHeader;
 
 import java.io.UnsupportedEncodingException;
@@ -93,9 +95,26 @@
 
     private String mBody;
 
+    /** Timestamp of ETWS primary notification with security. */
+    private long mPrimaryNotificationTimestamp;
+
+    /** 43 byte digital signature of ETWS primary notification with security. */
+    private byte[] mPrimaryNotificationDigitalSignature;
+
     private SmsCbMessage(byte[] pdu) throws IllegalArgumentException {
         mHeader = new SmsCbHeader(pdu);
-        parseBody(pdu);
+        if (mHeader.format == SmsCbHeader.FORMAT_ETWS_PRIMARY) {
+            mBody = "ETWS";
+            // ETWS primary notification with security is 56 octets in length
+            if (pdu.length >= SmsCbHeader.PDU_LENGTH_ETWS) {
+                mPrimaryNotificationTimestamp = getTimestampMillis(pdu);
+                mPrimaryNotificationDigitalSignature = new byte[43];
+                // digital signature starts after 6 byte header and 7 byte timestamp
+                System.arraycopy(pdu, 13, mPrimaryNotificationDigitalSignature, 0, 43);
+            }
+        } else {
+            parseBody(pdu);
+        }
     }
 
     /**
@@ -157,6 +176,55 @@
     }
 
     /**
+     * Get the format of this message.
+     * @return {@link SmsCbHeader#FORMAT_GSM}, {@link SmsCbHeader#FORMAT_UMTS}, or
+     *         {@link SmsCbHeader#FORMAT_ETWS_PRIMARY}
+     */
+    public int getMessageFormat() {
+        return mHeader.format;
+    }
+
+    /**
+     * For ETWS primary notifications, return the emergency user alert flag.
+     * @return true to notify terminal to activate emergency user alert; false otherwise
+     */
+    public boolean getEtwsEmergencyUserAlert() {
+        return mHeader.etwsEmergencyUserAlert;
+    }
+
+    /**
+     * For ETWS primary notifications, return the popup flag.
+     * @return true to notify terminal to activate display popup; false otherwise
+     */
+    public boolean getEtwsPopup() {
+        return mHeader.etwsPopup;
+    }
+
+    /**
+     * For ETWS primary notifications, return the warning type.
+     * @return a value such as {@link SmsCbConstants#ETWS_WARNING_TYPE_EARTHQUAKE}
+     */
+    public int getEtwsWarningType() {
+        return mHeader.etwsWarningType;
+    }
+
+    /**
+     * For ETWS primary notifications, return the Warning-Security-Information timestamp.
+     * @return a timestamp in System.currentTimeMillis() format.
+     */
+    public long getEtwsSecurityTimestamp() {
+        return mPrimaryNotificationTimestamp;
+    }
+
+    /**
+     * For ETWS primary notifications, return the 43 byte digital signature.
+     * @return a byte array containing a copy of the digital signature
+     */
+    public byte[] getEtwsSecuritySignature() {
+        return mPrimaryNotificationDigitalSignature.clone();
+    }
+
+    /**
      * Parse and unpack the body text according to the encoding in the DCS.
      * After completing successfully this method will have assigned the body
      * text into mBody, and optionally the language code into mLanguage
@@ -333,4 +401,59 @@
 
         return body;
     }
+
+    /**
+     * Parses an ETWS primary notification timestamp and returns a currentTimeMillis()-style
+     * timestamp. Copied from com.android.internal.telephony.gsm.SmsMessage.
+     * @param pdu the ETWS primary notification PDU to decode
+     * @return the UTC timestamp from the Warning-Security-Information parameter
+     */
+    private long getTimestampMillis(byte[] pdu) {
+        // Timestamp starts after CB header, in pdu[6]
+        int year = IccUtils.gsmBcdByteToInt(pdu[6]);
+        int month = IccUtils.gsmBcdByteToInt(pdu[7]);
+        int day = IccUtils.gsmBcdByteToInt(pdu[8]);
+        int hour = IccUtils.gsmBcdByteToInt(pdu[9]);
+        int minute = IccUtils.gsmBcdByteToInt(pdu[10]);
+        int second = IccUtils.gsmBcdByteToInt(pdu[11]);
+
+        // For the timezone, the most significant bit of the
+        // least significant nibble is the sign byte
+        // (meaning the max range of this field is 79 quarter-hours,
+        // which is more than enough)
+
+        byte tzByte = pdu[12];
+
+        // Mask out sign bit.
+        int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
+
+        timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+
+        Time time = new Time(Time.TIMEZONE_UTC);
+
+        // It's 2006.  Should I really support years < 2000?
+        time.year = year >= 90 ? year + 1900 : year + 2000;
+        time.month = month - 1;
+        time.monthDay = day;
+        time.hour = hour;
+        time.minute = minute;
+        time.second = second;
+
+        // Timezone offset is in quarter hours.
+        return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
+    }
+
+    /**
+     * Append text to the message body. This is used to concatenate multi-page GSM broadcasts.
+     * @param body the text to append to this message
+     */
+    public void appendToBody(String body) {
+        mBody = mBody + body;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage +
+                ", body=\"" + mBody + "\"}";
+    }
 }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 0ecd854..480186c 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -21,7 +21,6 @@
 import android.os.ServiceManager;
 import android.text.TextUtils;
 
-import com.android.internal.telephony.EncodeException;
 import com.android.internal.telephony.ISms;
 import com.android.internal.telephony.IccConstants;
 import com.android.internal.telephony.SmsRawData;
@@ -348,7 +347,7 @@
      * message identifier. Note that if two different clients enable the same
      * message identifier, they must both disable it for the device to stop
      * receiving those messages. All received messages will be broadcast in an
-     * intent with the action "android.provider.telephony.SMS_CB_RECEIVED".
+     * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
      * Note: This call is blocking, callers may want to avoid calling it from
      * the main thread of an application.
      *
@@ -404,6 +403,68 @@
     }
 
     /**
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable the same
+     * message identifier, they must both disable it for the device to stop
+     * receiving those messages. All received messages will be broadcast in an
+     * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     * @see #disableCellBroadcastRange(int, int)
+     *
+     * {@hide}
+     */
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+        boolean success = false;
+
+        try {
+            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            if (iccISms != null) {
+                success = iccISms.enableCellBroadcastRange(startMessageId, endMessageId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
+     * Disable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable the same
+     * message identifier, they must both disable it for the device to stop
+     * receiving those messages.
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcastRange(int, int)
+     *
+     * {@hide}
+     */
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+        boolean success = false;
+
+        try {
+            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            if (iccISms != null) {
+                success = iccISms.disableCellBroadcastRange(startMessageId, endMessageId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
      * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
      * records returned by <code>getAllMessagesFromIcc()</code>
      *
diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
index 57d73e3..8ea1f8d 100644
--- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
@@ -83,6 +83,8 @@
      * GSM_EXTENDED_ESCAPE if this character is in the extended table.
      * In this case, you must call charToGsmExtended() for the value
      * that should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string.
+     * @param c the character to convert
+     * @return the GSM 7 bit table index for the specified character
      */
     public static int
     charToGsm(char c) {
@@ -96,12 +98,15 @@
 
     /**
      * Converts a char to a GSM 7 bit table index.
+     * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table.
+     * In this case, you must call charToGsmExtended() for the value that
+     * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string.
+     *
+     * @param c the character to convert
      * @param throwException If true, throws EncodeException on invalid char.
      *   If false, returns GSM alphabet ' ' char.
-     *
-     * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table
-     * In this case, you must call charToGsmExtended() for the value that
-     * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string
+     * @throws EncodeException encode error when throwException is true
+     * @return the GSM 7 bit table index for the specified character
      */
     public static int
     charToGsm(char c, boolean throwException) throws EncodeException {
@@ -130,6 +135,8 @@
      * Converts a char to an extended GSM 7 bit table index.
      * Extended chars should be escaped with GSM_EXTENDED_ESCAPE.
      * Returns ' ' in GSM alphabet if there's no possible match.
+     * @param c the character to convert
+     * @return the GSM 7 bit extended table index for the specified character
      */
     public static int
     charToGsmExtended(char c) {
@@ -152,6 +159,9 @@
      * gsmExtendedToChar().
      *
      * If an unmappable value is passed (one greater than 127), ' ' is returned.
+     *
+     * @param gsmChar the GSM 7 bit table index to convert
+     * @return the decoded character
      */
     public static char
     gsmToChar(int gsmChar) {
@@ -171,6 +181,9 @@
      *
      * If an unmappable value is passed, the character from the GSM 7 bit
      * default table will be used (table 6.2.1.1 of TS 23.038).
+     *
+     * @param gsmChar the GSM 7 bit extended table index to convert
+     * @return the decoded character
      */
     public static char
     gsmExtendedToChar(int gsmChar) {
@@ -241,6 +254,26 @@
      * septets.
      *
      * @param data the data string to encode
+     * @return the encoded string
+     * @throws EncodeException if String is too large to encode
+     */
+    public static byte[] stringToGsm7BitPacked(String data)
+            throws EncodeException {
+        return stringToGsm7BitPacked(data, 0, true, 0, 0);
+    }
+
+    /**
+     * Converts a String into a byte array containing
+     * the 7-bit packed GSM Alphabet representation of the string.
+     *
+     * Unencodable chars are encoded as spaces
+     *
+     * Byte 0 in the returned byte array is the count of septets used
+     * The returned byte array is the minimum size required to store
+     * the packed septets. The returned array cannot contain more than 255
+     * septets.
+     *
+     * @param data the data string to encode
      * @param languageTable the 7 bit language table, or 0 for the default GSM alphabet
      * @param languageShiftTable the 7 bit single shift language table, or 0 for the default
      *     GSM extension table
@@ -446,6 +479,11 @@
      *
      * Field may be padded with trailing 0xff's. The decode stops
      * at the first 0xff encountered.
+     *
+     * @param data the byte array to decode
+     * @param offset array offset for the first character to decode
+     * @param length the number of bytes to decode
+     * @return the decoded string
      */
     public static String
     gsm8BitUnpackedToString(byte[] data, int offset, int length) {
@@ -494,6 +532,8 @@
     /**
      * Convert a string into an 8-bit unpacked GSM alphabet byte array.
      * Always uses GSM default 7-bit alphabet and extension table.
+     * @param s the string to encode
+     * @return the 8-bit GSM encoded byte array for the string
      */
     public static byte[]
     stringToGsm8BitPacked(String s) {
@@ -512,11 +552,13 @@
 
     /**
      * Write a String into a GSM 8-bit unpacked field of
-     * @param length size at @param offset in @param dest
-     *
      * Field is padded with 0xff's, string is truncated if necessary
+     *
+     * @param s the string to encode
+     * @param dest the destination byte array
+     * @param offset the starting offset for the encoded string
+     * @param length the maximum number of bytes to write
      */
-
     public static void
     stringToGsm8BitUnpackedField(String s, byte dest[], int offset, int length) {
         int outByteIndex = offset;
@@ -558,6 +600,8 @@
     /**
      * Returns the count of 7-bit GSM alphabet characters
      * needed to represent this character. Counts unencodable char as 1 septet.
+     * @param c the character to examine
+     * @return the number of septets for this character
      */
     public static int
     countGsmSeptets(char c) {
@@ -572,8 +616,11 @@
     /**
      * Returns the count of 7-bit GSM alphabet characters
      * needed to represent this character using the default 7 bit GSM alphabet.
+     * @param c the character to examine
      * @param throwsException If true, throws EncodeException if unencodable
-     * char. Otherwise, counts invalid char as 1 septet
+     * char. Otherwise, counts invalid char as 1 septet.
+     * @return the number of septets for this character
+     * @throws EncodeException the character can't be encoded and throwsException is true
      */
     public static int
     countGsmSeptets(char c, boolean throwsException) throws EncodeException {
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 90de5e1..735f986 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -170,4 +170,32 @@
      */
     boolean disableCellBroadcast(int messageIdentifier);
 
+    /**
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable
+     * a message identifier range, they must both disable it for the device
+     * to stop receiving those messages.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     *
+     * @see #disableCellBroadcastRange(int, int)
+     */
+    boolean enableCellBroadcastRange(int startMessageId, int endMessageId);
+
+    /**
+     * Disable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range. Note that if two different clients enable
+     * a message identifier range, they must both disable it for the device
+     * to stop receiving those messages.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcastRange(int, int)
+     */
+    boolean disableCellBroadcastRange(int startMessageId, int endMessageId);
+
 }
diff --git a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java
index 5049249..54de508 100644
--- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java
+++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java
@@ -76,4 +76,13 @@
         return mIccSmsInterfaceManager.disableCellBroadcast(messageIdentifier);
     }
 
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId)
+            throws android.os.RemoteException {
+        return mIccSmsInterfaceManager.enableCellBroadcastRange(startMessageId, endMessageId);
+    }
+
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId)
+            throws android.os.RemoteException {
+        return mIccSmsInterfaceManager.disableCellBroadcastRange(startMessageId, endMessageId);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/IntRangeManager.java b/telephony/java/com/android/internal/telephony/IntRangeManager.java
new file mode 100644
index 0000000..970bc44
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IntRangeManager.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2011 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.telephony;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Clients can enable reception of SMS-CB messages for specific ranges of
+ * message identifiers (channels). This class keeps track of the currently
+ * enabled message identifiers and calls abstract methods to update the
+ * radio when the range of enabled message identifiers changes.
+ *
+ * An update is a call to {@link #startUpdate} followed by zero or more
+ * calls to {@link #addRange} followed by a call to {@link #finishUpdate}.
+ * Calls to {@link #enableRange} and {@link #disableRange} will perform
+ * an incremental update operation if the enabled ranges have changed.
+ * A full update operation (i.e. after a radio reset) can be performed
+ * by a call to {@link #updateRanges}.
+ *
+ * Clients are identified by String (the name associated with the User ID
+ * of the caller) so that a call to remove a range can be mapped to the
+ * client that enabled that range (or else rejected).
+ */
+public abstract class IntRangeManager {
+
+    /**
+     * Initial capacity for IntRange clients array list. There will be
+     * few cell broadcast listeners on a typical device, so this can be small.
+     */
+    private static final int INITIAL_CLIENTS_ARRAY_SIZE = 4;
+
+    /**
+     * One or more clients forming the continuous range [startId, endId].
+     * <p>When a client is added, the IntRange may merge with one or more
+     * adjacent IntRanges to form a single combined IntRange.
+     * <p>When a client is removed, the IntRange may divide into several
+     * non-contiguous IntRanges.
+     */
+    private class IntRange {
+        int startId;
+        int endId;
+        // sorted by earliest start id
+        final ArrayList<ClientRange> clients;
+
+        /**
+         * Create a new IntRange with a single client.
+         * @param startId the first id included in the range
+         * @param endId the last id included in the range
+         * @param client the client requesting the enabled range
+         */
+        IntRange(int startId, int endId, String client) {
+            this.startId = startId;
+            this.endId = endId;
+            clients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE);
+            clients.add(new ClientRange(startId, endId, client));
+        }
+
+        /**
+         * Create a new IntRange for an existing ClientRange.
+         * @param clientRange the initial ClientRange to add
+         */
+        IntRange(ClientRange clientRange) {
+            startId = clientRange.startId;
+            endId = clientRange.endId;
+            clients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE);
+            clients.add(clientRange);
+        }
+
+        /**
+         * Create a new IntRange from an existing IntRange. This is used for
+         * removing a ClientRange, because new IntRanges may need to be created
+         * for any gaps that open up after the ClientRange is removed. A copy
+         * is made of the elements of the original IntRange preceding the element
+         * that is being removed. The following elements will be added to this
+         * IntRange or to a new IntRange when a gap is found.
+         * @param intRange the original IntRange to copy elements from
+         * @param numElements the number of elements to copy from the original
+         */
+        IntRange(IntRange intRange, int numElements) {
+            this.startId = intRange.startId;
+            this.endId = intRange.endId;
+            this.clients = new ArrayList<ClientRange>(intRange.clients.size());
+            for (int i=0; i < numElements; i++) {
+                this.clients.add(intRange.clients.get(i));
+            }
+        }
+
+        /**
+         * Insert new ClientRange in order by start id.
+         * <p>If the new ClientRange is known to be sorted before or after the
+         * existing ClientRanges, or at a particular index, it can be added
+         * to the clients array list directly, instead of via this method.
+         * <p>Note that this can be changed from linear to binary search if the
+         * number of clients grows large enough that it would make a difference.
+         * @param range the new ClientRange to insert
+         */
+        void insert(ClientRange range) {
+            int len = clients.size();
+            for (int i=0; i < len; i++) {
+                ClientRange nextRange = clients.get(i);
+                if (range.startId <= nextRange.startId) {
+                    // ignore duplicate ranges from the same client
+                    if (!range.equals(nextRange)) {
+                        clients.add(i, range);
+                    }
+                    return;
+                }
+            }
+            clients.add(range);    // append to end of list
+        }
+    }
+
+    /**
+     * The message id range for a single client.
+     */
+    private class ClientRange {
+        final int startId;
+        final int endId;
+        final String client;
+
+        ClientRange(int startId, int endId, String client) {
+            this.startId = startId;
+            this.endId = endId;
+            this.client = client;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o != null && o instanceof ClientRange) {
+                ClientRange other = (ClientRange) o;
+                return startId == other.startId &&
+                        endId == other.endId &&
+                        client.equals(other.client);
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return (startId * 31 + endId) * 31 + client.hashCode();
+        }
+    }
+
+    /**
+     * List of integer ranges, one per client, sorted by start id.
+     */
+    private ArrayList<IntRange> mRanges = new ArrayList<IntRange>();
+
+    protected IntRangeManager() {}
+
+    /**
+     * Enable a range for the specified client and update ranges
+     * if necessary. If {@link #finishUpdate} returns failure,
+     * false is returned and the range is not added.
+     *
+     * @param startId the first id included in the range
+     * @param endId the last id included in the range
+     * @param client the client requesting the enabled range
+     * @return true if successful, false otherwise
+     */
+    public synchronized boolean enableRange(int startId, int endId, String client) {
+        int len = mRanges.size();
+
+        // empty range list: add the initial IntRange
+        if (len == 0) {
+            if (tryAddSingleRange(startId, endId, true)) {
+                mRanges.add(new IntRange(startId, endId, client));
+                return true;
+            } else {
+                return false;   // failed to update radio
+            }
+        }
+
+        for (int startIndex = 0; startIndex < len; startIndex++) {
+            IntRange range = mRanges.get(startIndex);
+            if (startId < range.startId) {
+                // test if new range completely precedes this range
+                // note that [1, 4] and [5, 6] coalesce to [1, 6]
+                if ((endId + 1) < range.startId) {
+                    // insert new int range before previous first range
+                    if (tryAddSingleRange(startId, endId, true)) {
+                        mRanges.add(startIndex, new IntRange(startId, endId, client));
+                        return true;
+                    } else {
+                        return false;   // failed to update radio
+                    }
+                } else if (endId <= range.endId) {
+                    // extend the start of this range
+                    if (tryAddSingleRange(startId, range.startId - 1, true)) {
+                        range.startId = startId;
+                        range.clients.add(0, new ClientRange(startId, endId, client));
+                        return true;
+                    } else {
+                        return false;   // failed to update radio
+                    }
+                } else {
+                    // find last range that can coalesce into the new combined range
+                    for (int endIndex = startIndex+1; endIndex < len; endIndex++) {
+                        IntRange endRange = mRanges.get(endIndex);
+                        if ((endId + 1) < endRange.startId) {
+                            // try to add entire new range
+                            if (tryAddSingleRange(startId, endId, true)) {
+                                range.startId = startId;
+                                range.endId = endId;
+                                // insert new ClientRange before existing ranges
+                                range.clients.add(0, new ClientRange(startId, endId, client));
+                                // coalesce range with following ranges up to endIndex-1
+                                // remove each range after adding its elements, so the index
+                                // of the next range to join is always startIndex+1.
+                                // i is the index if no elements were removed: we only care
+                                // about the number of loop iterations, not the value of i.
+                                int joinIndex = startIndex + 1;
+                                for (int i = joinIndex; i < endIndex; i++) {
+                                    IntRange joinRange = mRanges.get(joinIndex);
+                                    range.clients.addAll(joinRange.clients);
+                                    mRanges.remove(joinRange);
+                                }
+                                return true;
+                            } else {
+                                return false;   // failed to update radio
+                            }
+                        } else if (endId <= endRange.endId) {
+                            // add range from start id to start of last overlapping range,
+                            // values from endRange.startId to endId are already enabled
+                            if (tryAddSingleRange(startId, endRange.startId - 1, true)) {
+                                range.startId = startId;
+                                range.endId = endRange.endId;
+                                // insert new ClientRange before existing ranges
+                                range.clients.add(0, new ClientRange(startId, endId, client));
+                                // coalesce range with following ranges up to endIndex
+                                // remove each range after adding its elements, so the index
+                                // of the next range to join is always startIndex+1.
+                                // i is the index if no elements were removed: we only care
+                                // about the number of loop iterations, not the value of i.
+                                int joinIndex = startIndex + 1;
+                                for (int i = joinIndex; i <= endIndex; i++) {
+                                    IntRange joinRange = mRanges.get(joinIndex);
+                                    range.clients.addAll(joinRange.clients);
+                                    mRanges.remove(joinRange);
+                                }
+                                return true;
+                            } else {
+                                return false;   // failed to update radio
+                            }
+                        }
+                    }
+
+                    // endId extends past all existing IntRanges: combine them all together
+                    if (tryAddSingleRange(startId, endId, true)) {
+                        range.startId = startId;
+                        range.endId = endId;
+                        // insert new ClientRange before existing ranges
+                        range.clients.add(0, new ClientRange(startId, endId, client));
+                        // coalesce range with following ranges up to len-1
+                        // remove each range after adding its elements, so the index
+                        // of the next range to join is always startIndex+1.
+                        // i is the index if no elements were removed: we only care
+                        // about the number of loop iterations, not the value of i.
+                        int joinIndex = startIndex + 1;
+                        for (int i = joinIndex; i < len; i++) {
+                            IntRange joinRange = mRanges.get(joinIndex);
+                            range.clients.addAll(joinRange.clients);
+                            mRanges.remove(joinRange);
+                        }
+                        return true;
+                    } else {
+                        return false;   // failed to update radio
+                    }
+                }
+            } else if ((startId + 1) <= range.endId) {
+                if (endId <= range.endId) {
+                    // completely contained in existing range; no radio changes
+                    range.insert(new ClientRange(startId, endId, client));
+                    return true;
+                } else {
+                    // find last range that can coalesce into the new combined range
+                    int endIndex = startIndex;
+                    for (int testIndex = startIndex+1; testIndex < len; testIndex++) {
+                        IntRange testRange = mRanges.get(testIndex);
+                        if ((endId + 1) < testRange.startId) {
+                            break;
+                        } else {
+                            endIndex = testIndex;
+                        }
+                    }
+                    // no adjacent IntRanges to combine
+                    if (endIndex == startIndex) {
+                        // add range from range.endId+1 to endId,
+                        // values from startId to range.endId are already enabled
+                        if (tryAddSingleRange(range.endId + 1, endId, true)) {
+                            range.endId = endId;
+                            range.insert(new ClientRange(startId, endId, client));
+                            return true;
+                        } else {
+                            return false;   // failed to update radio
+                        }
+                    }
+                    // get last range to coalesce into start range
+                    IntRange endRange = mRanges.get(endIndex);
+                    // Values from startId to range.endId have already been enabled.
+                    // if endId > endRange.endId, then enable range from range.endId+1 to endId,
+                    // else enable range from range.endId+1 to endRange.startId-1, because
+                    // values from endRange.startId to endId have already been added.
+                    int newRangeEndId = (endId <= endRange.endId) ? endRange.startId - 1 : endId;
+                    if (tryAddSingleRange(range.endId + 1, newRangeEndId, true)) {
+                        range.endId = endId;
+                        // insert new ClientRange in place
+                        range.insert(new ClientRange(startId, endId, client));
+                        // coalesce range with following ranges up to endIndex-1
+                        // remove each range after adding its elements, so the index
+                        // of the next range to join is always startIndex+1 (joinIndex).
+                        // i is the index if no elements had been removed: we only care
+                        // about the number of loop iterations, not the value of i.
+                        int joinIndex = startIndex + 1;
+                        for (int i = joinIndex; i < endIndex; i++) {
+                            IntRange joinRange = mRanges.get(joinIndex);
+                            range.clients.addAll(joinRange.clients);
+                            mRanges.remove(joinRange);
+                        }
+                        return true;
+                    } else {
+                        return false;   // failed to update radio
+                    }
+                }
+            }
+        }
+
+        // append new range after existing IntRanges
+        if (tryAddSingleRange(startId, endId, true)) {
+            mRanges.add(new IntRange(startId, endId, client));
+            return true;
+        } else {
+            return false;   // failed to update radio
+        }
+    }
+
+    /**
+     * Disable a range for the specified client and update ranges
+     * if necessary. If {@link #finishUpdate} returns failure,
+     * false is returned and the range is not removed.
+     *
+     * @param startId the first id included in the range
+     * @param endId the last id included in the range
+     * @param client the client requesting to disable the range
+     * @return true if successful, false otherwise
+     */
+    public synchronized boolean disableRange(int startId, int endId, String client) {
+        int len = mRanges.size();
+
+        for (int i=0; i < len; i++) {
+            IntRange range = mRanges.get(i);
+            if (startId < range.startId) {
+                return false;   // not found
+            } else if (endId <= range.endId) {
+                // found the IntRange that encloses the client range, if any
+                // search for it in the clients list
+                ArrayList<ClientRange> clients = range.clients;
+
+                // handle common case of IntRange containing one ClientRange
+                int crLength = clients.size();
+                if (crLength == 1) {
+                    ClientRange cr = clients.get(0);
+                    if (cr.startId == startId && cr.endId == endId && cr.client.equals(client)) {
+                        // disable range in radio then remove the entire IntRange
+                        if (tryAddSingleRange(startId, endId, false)) {
+                            mRanges.remove(i);
+                            return true;
+                        } else {
+                            return false;   // failed to update radio
+                        }
+                    } else {
+                        return false;   // not found
+                    }
+                }
+
+                // several ClientRanges: remove one, potentially splitting into many IntRanges.
+                // Save the original start and end id for the original IntRange
+                // in case the radio update fails and we have to revert it. If the
+                // update succeeds, we remove the client range and insert the new IntRanges.
+                int largestEndId = Integer.MIN_VALUE;  // largest end identifier found
+                boolean updateStarted = false;
+
+                for (int crIndex=0; crIndex < crLength; crIndex++) {
+                    ClientRange cr = clients.get(crIndex);
+                    if (cr.startId == startId && cr.endId == endId && cr.client.equals(client)) {
+                        // found the ClientRange to remove, check if it's the last in the list
+                        if (crIndex == crLength - 1) {
+                            if (range.endId == largestEndId) {
+                                // no channels to remove from radio; return success
+                                clients.remove(crIndex);
+                                return true;
+                            } else {
+                                // disable the channels at the end and lower the end id
+                                if (tryAddSingleRange(largestEndId + 1, range.endId, false)) {
+                                    clients.remove(crIndex);
+                                    range.endId = largestEndId;
+                                    return true;
+                                } else {
+                                    return false;
+                                }
+                            }
+                        }
+
+                        // copy the IntRange so that we can remove elements and modify the
+                        // start and end id's in the copy, leaving the original unmodified
+                        // until after the radio update succeeds
+                        IntRange rangeCopy = new IntRange(range, crIndex);
+
+                        if (crIndex == 0) {
+                            // removing the first ClientRange, so we may need to increase
+                            // the start id of the IntRange.
+                            // We know there are at least two ClientRanges in the list,
+                            // so clients.get(1) should always succeed.
+                            int nextStartId = clients.get(1).startId;
+                            if (nextStartId != range.startId) {
+                                startUpdate();
+                                updateStarted = true;
+                                addRange(range.startId, nextStartId - 1, false);
+                                rangeCopy.startId = nextStartId;
+                            }
+                            // init largestEndId
+                            largestEndId = clients.get(1).endId;
+                        }
+
+                        // go through remaining ClientRanges, creating new IntRanges when
+                        // there is a gap in the sequence. After radio update succeeds,
+                        // remove the original IntRange and append newRanges to mRanges.
+                        // Otherwise, leave the original IntRange in mRanges and return false.
+                        ArrayList<IntRange> newRanges = new ArrayList<IntRange>();
+
+                        IntRange currentRange = rangeCopy;
+                        for (int nextIndex = crIndex + 1; nextIndex < crLength; nextIndex++) {
+                            ClientRange nextCr = clients.get(nextIndex);
+                            if (nextCr.startId > largestEndId + 1) {
+                                if (!updateStarted) {
+                                    startUpdate();
+                                    updateStarted = true;
+                                }
+                                addRange(largestEndId + 1, nextCr.startId - 1, false);
+                                currentRange.endId = largestEndId;
+                                newRanges.add(currentRange);
+                                currentRange = new IntRange(nextCr);
+                            } else {
+                                currentRange.clients.add(nextCr);
+                            }
+                            if (nextCr.endId > largestEndId) {
+                                largestEndId = nextCr.endId;
+                            }
+                        }
+
+                        // remove any channels between largestEndId and endId
+                        if (largestEndId < endId) {
+                            if (!updateStarted) {
+                                startUpdate();
+                                updateStarted = true;
+                            }
+                            addRange(largestEndId + 1, endId, false);
+                            currentRange.endId = largestEndId;
+                        }
+                        newRanges.add(currentRange);
+
+                        if (updateStarted && !finishUpdate()) {
+                            return false;   // failed to update radio
+                        }
+
+                        // replace the original IntRange with newRanges
+                        mRanges.remove(i);
+                        mRanges.addAll(i, newRanges);
+                        return true;
+                    } else {
+                        // not the ClientRange to remove; save highest end ID seen so far
+                        if (cr.endId > largestEndId) {
+                            largestEndId = cr.endId;
+                        }
+                    }
+                }
+            }
+        }
+
+        return false;   // not found
+    }
+
+    /**
+     * Perform a complete update operation (enable all ranges). Useful
+     * after a radio reset. Calls {@link #startUpdate}, followed by zero or
+     * more calls to {@link #addRange}, followed by {@link #finishUpdate}.
+     * @return true if successful, false otherwise
+     */
+    public boolean updateRanges() {
+        startUpdate();
+        Iterator<IntRange> iterator = mRanges.iterator();
+        if (iterator.hasNext()) {
+            IntRange range = iterator.next();
+            int start = range.startId;
+            int end = range.endId;
+            // accumulate ranges of [startId, endId]
+            while (iterator.hasNext()) {
+                IntRange nextNode = iterator.next();
+                // [startIdA, endIdA], [endIdA + 1, endIdB] -> [startIdA, endIdB]
+                if (nextNode.startId <= (end + 1)) {
+                    if (nextNode.endId > end) {
+                        end = nextNode.endId;
+                    }
+                } else {
+                    addRange(start, end, true);
+                    start = nextNode.startId;
+                    end = nextNode.endId;
+                }
+            }
+            // add final range
+            addRange(start, end, true);
+        }
+        return finishUpdate();
+    }
+
+    /**
+     * Enable or disable a single range of message identifiers.
+     * @param startId the first id included in the range
+     * @param endId the last id included in the range
+     * @param selected true to enable range, false to disable range
+     * @return true if successful, false otherwise
+     */
+    private boolean tryAddSingleRange(int startId, int endId, boolean selected) {
+        startUpdate();
+        addRange(startId, endId, selected);
+        return finishUpdate();
+    }
+
+    /**
+     * Called when the list of enabled ranges has changed. This will be
+     * followed by zero or more calls to {@link #addRange} followed by
+     * a call to {@link #finishUpdate}.
+     */
+    protected abstract void startUpdate();
+
+    /**
+     * Called after {@link #startUpdate} to indicate a range of enabled
+     * or disabled values.
+     *
+     * @param startId the first id included in the range
+     * @param endId the last id included in the range
+     * @param selected true to enable range, false to disable range
+     */
+    protected abstract void addRange(int startId, int endId, boolean selected);
+
+    /**
+     * Called to indicate the end of a range update started by the
+     * previous call to {@link #startUpdate}.
+     * @return true if successful, false otherwise
+     */
+    protected abstract boolean finishUpdate();
+}
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
old mode 100755
new mode 100644
index ad34550..69dd029
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -32,11 +32,9 @@
 import android.database.SQLException;
 import android.net.Uri;
 import android.os.AsyncResult;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
-import android.os.StatFs;
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
 import android.provider.Settings;
@@ -878,37 +876,6 @@
     protected abstract void sendMultipartSms (SmsTracker tracker);
 
     /**
-     * Activate or deactivate cell broadcast SMS.
-     *
-     * @param activate
-     *            0 = activate, 1 = deactivate
-     * @param response
-     *            Callback message is empty on completion
-     */
-    protected abstract void activateCellBroadcastSms(int activate, Message response);
-
-    /**
-     * Query the current configuration of cell broadcast SMS.
-     *
-     * @param response
-     *            Callback message contains the configuration from the modem on completion
-     *            @see #setCellBroadcastConfig
-     */
-    protected abstract void getCellBroadcastSmsConfig(Message response);
-
-    /**
-     * Configure cell broadcast SMS.
-     *
-     * @param configValuesArray
-     *          The first element defines the number of triples that follow.
-     *          A triple is made up of the service category, the language identifier
-     *          and a boolean that specifies whether the category is set active.
-     * @param response
-     *            Callback message is empty on completion
-     */
-    protected abstract void setCellBroadcastConfig(int[] configValuesArray, Message response);
-
-    /**
      * Send an acknowledge message.
      * @param success indicates that last message was successfully received.
      * @param result result code indicating any error
@@ -1014,14 +981,21 @@
 
     protected abstract void handleBroadcastSms(AsyncResult ar);
 
-    protected void dispatchBroadcastPdus(byte[][] pdus) {
-        Intent intent = new Intent("android.provider.telephony.SMS_CB_RECEIVED");
-        intent.putExtra("pdus", pdus);
+    protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) {
+        if (isEmergencyMessage) {
+            Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
+            intent.putExtra("pdus", pdus);
+            if (Config.LOGD)
+                Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus");
 
-        if (Config.LOGD)
-            Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
+            dispatch(intent, "android.permission.RECEIVE_EMERGENCY_BROADCAST");
+        } else {
+            Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
+            intent.putExtra("pdus", pdus);
+            if (Config.LOGD)
+                Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
 
-        dispatch(intent, "android.permission.RECEIVE_SMS");
+            dispatch(intent, "android.permission.RECEIVE_SMS");
+        }
     }
-
 }
diff --git a/telephony/java/com/android/internal/telephony/WapPushOverSms.java b/telephony/java/com/android/internal/telephony/WapPushOverSms.java
old mode 100644
new mode 100755
diff --git a/telephony/java/com/android/internal/telephony/WspTypeDecoder.java b/telephony/java/com/android/internal/telephony/WspTypeDecoder.java
old mode 100644
new mode 100755
index c8dd718..73260fb
--- a/telephony/java/com/android/internal/telephony/WspTypeDecoder.java
+++ b/telephony/java/com/android/internal/telephony/WspTypeDecoder.java
@@ -194,6 +194,7 @@
 
     public static final String CONTENT_TYPE_B_PUSH_CO = "application/vnd.wap.coc";
     public static final String CONTENT_TYPE_B_MMS = "application/vnd.wap.mms-message";
+    public static final String CONTENT_TYPE_B_PUSH_SYNCML_NOTI = "application/vnd.syncml.notification";
 
     byte[] wspData;
     int    dataLength;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 3890a98..1efae21 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -1163,7 +1163,8 @@
      * @param response Callback message is empty on completion
      */
     public void activateCellBroadcastSms(int activate, Message response) {
-        mSMS.activateCellBroadcastSms(activate, response);
+        Log.e(LOG_TAG, "[CDMAPhone] activateCellBroadcastSms() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
     /**
@@ -1172,7 +1173,8 @@
      * @param response Callback message is empty on completion
      */
     public void getCellBroadcastSmsConfig(Message response) {
-        mSMS.getCellBroadcastSmsConfig(response);
+        Log.e(LOG_TAG, "[CDMAPhone] getCellBroadcastSmsConfig() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
     /**
@@ -1181,7 +1183,8 @@
      * @param response Callback message is empty on completion
      */
     public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
-        mSMS.setCellBroadcastConfig(configValuesArray, response);
+        Log.e(LOG_TAG, "[CDMAPhone] setCellBroadcastSmsConfig() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
     private static final String IS683A_FEATURE_CODE = "*228";
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
old mode 100644
new mode 100755
index d6fc134..a04cafc
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -42,6 +42,7 @@
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
 import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.WspTypeDecoder;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
 import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.util.HexDump;
@@ -51,6 +52,8 @@
 import java.util.Arrays;
 import java.util.HashMap;
 
+import android.content.res.Resources;
+
 
 final class CdmaSMSDispatcher extends SMSDispatcher {
     private static final String TAG = "CDMA";
@@ -58,6 +61,9 @@
     private byte[] mLastDispatchedSmsFingerprint;
     private byte[] mLastAcknowledgedSmsFingerprint;
 
+    private boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
+            com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
+
     CdmaSMSDispatcher(CDMAPhone phone) {
         super(phone);
     }
@@ -245,6 +251,13 @@
             sourcePort |= 0xFF & pdu[index++];
             destinationPort = (0xFF & pdu[index++]) << 8;
             destinationPort |= 0xFF & pdu[index++];
+            // Some carriers incorrectly send duplicate port fields in omadm wap pushes.
+            // If configured, check for that here
+            if (mCheckForDuplicatePortsInOmadmWapPush) {
+                if (checkDuplicatePortOmadmWappush(pdu,index)) {
+                    index = index + 4; // skip duplicate port fields
+                }
+            }
         }
 
         // Lookup all other related parts
@@ -265,7 +278,7 @@
             if (cursorCount != totalSegments - 1) {
                 // We don't have all the parts yet, store this one away
                 ContentValues values = new ContentValues();
-                values.put("date", new Long(0));
+                values.put("date", (long) 0);
                 values.put("pdu", HexDump.toHexString(pdu, index, pdu.length - index));
                 values.put("address", address);
                 values.put("reference_number", referenceNumber);
@@ -468,21 +481,6 @@
         }
     }
 
-    /** {@inheritDoc} */
-    protected void activateCellBroadcastSms(int activate, Message response) {
-        mCm.setCdmaBroadcastActivation((activate == 0), response);
-    }
-
-    /** {@inheritDoc} */
-    protected void getCellBroadcastSmsConfig(Message response) {
-        mCm.getCdmaBroadcastConfig(response);
-    }
-
-    /** {@inheritDoc} */
-    protected void setCellBroadcastConfig(int[] configValuesArray, Message response) {
-        mCm.setCdmaBroadcastConfig(configValuesArray, response);
-    }
-
     protected void handleBroadcastSms(AsyncResult ar) {
         // Not supported
         Log.e(TAG, "Error! Not implemented for CDMA.");
@@ -503,4 +501,42 @@
             return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM;
         }
     }
+
+    /**
+     * Optional check to see if the received WapPush is an OMADM notification with erroneous
+     * extra port fields.
+     * - Some carriers make this mistake.
+     * ex: MSGTYPE-TotalSegments-CurrentSegment
+     *       -SourcePortDestPort-SourcePortDestPort-OMADM PDU
+     * @param origPdu The WAP-WDP PDU segment
+     * @param index Current Index while parsing the PDU.
+     * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
+     *         False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
+     */
+    private boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) {
+        index += 4;
+        byte[] omaPdu = new byte[origPdu.length - index];
+        System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length);
+
+        WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu);
+        int wspIndex = 2;
+
+        // Process header length field
+        if (pduDecoder.decodeUintvarInteger(wspIndex) == false) {
+            return false;
+        }
+
+        wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field
+
+        // Process content type field
+        if (pduDecoder.decodeContentType(wspIndex) == false) {
+            return false;
+        }
+
+        String mimeType = pduDecoder.getValueString();
+        if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI)) {
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
index f4a6d11..7b42b06 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
@@ -204,6 +204,18 @@
         return false;
     }
 
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+        // Not implemented
+        Log.e(LOG_TAG, "Error! Not implemented for CDMA.");
+        return false;
+    }
+
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+        // Not implemented
+        Log.e(LOG_TAG, "Error! Not implemented for CDMA.");
+        return false;
+    }
+
     protected void log(String msg) {
         Log.d(LOG_TAG, "[RuimSmsInterfaceManager] " + msg);
     }
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 54cf612..676a828 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.SystemProperties;
+import android.telephony.PhoneNumberUtils;
 import android.text.format.Time;
 import android.util.Config;
 import android.util.Log;
@@ -811,7 +812,12 @@
          * mechanism, and avoid null pointer exceptions.
          */
 
-        CdmaSmsAddress destAddr = CdmaSmsAddress.parse(destAddrStr);
+        /**
+         * North America Plus Code :
+         * Convert + code to 011 and dial out for international SMS
+         */
+        CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode(destAddrStr));
         if (destAddr == null) return null;
 
         BearerData bearerData = new BearerData();
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index 3959c67..c5be856 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -1471,16 +1471,35 @@
         return this.mIccFileHandler;
     }
 
+    /**
+     * Activate or deactivate cell broadcast SMS.
+     *
+     * @param activate 0 = activate, 1 = deactivate
+     * @param response Callback message is empty on completion
+     */
     public void activateCellBroadcastSms(int activate, Message response) {
-        Log.e(LOG_TAG, "Error! This functionality is not implemented for GSM.");
+        Log.e(LOG_TAG, "[GSMPhone] activateCellBroadcastSms() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
+    /**
+     * Query the current configuration of cdma cell broadcast SMS.
+     *
+     * @param response Callback message is empty on completion
+     */
     public void getCellBroadcastSmsConfig(Message response) {
-        Log.e(LOG_TAG, "Error! This functionality is not implemented for GSM.");
+        Log.e(LOG_TAG, "[GSMPhone] getCellBroadcastSmsConfig() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
-    public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response){
-        Log.e(LOG_TAG, "Error! This functionality is not implemented for GSM.");
+    /**
+     * Configure cdma cell broadcast SMS.
+     *
+     * @param response Callback message is empty on completion
+     */
+    public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
+        Log.e(LOG_TAG, "[GSMPhone] setCellBroadcastSmsConfig() is obsolete; use SmsManager");
+        response.sendToTarget();
     }
 
     public boolean isCspPlmnEnabled() {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
old mode 100755
new mode 100644
index 14ad59a..de769d1
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -350,6 +350,7 @@
      *
      * @param tracker holds the multipart Sms tracker ready to be sent
      */
+    @Override
     protected void sendMultipartSms (SmsTracker tracker) {
         ArrayList<String> parts;
         ArrayList<PendingIntent> sentIntents;
@@ -370,6 +371,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
         // FIXME unit test leaves cm == null. this should change
         if (mCm != null) {
@@ -377,27 +379,6 @@
         }
     }
 
-    /** {@inheritDoc} */
-    protected void activateCellBroadcastSms(int activate, Message response) {
-        // Unless CBS is implemented for GSM, this point should be unreachable.
-        Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
-        response.recycle();
-    }
-
-    /** {@inheritDoc} */
-    protected void getCellBroadcastSmsConfig(Message response){
-        // Unless CBS is implemented for GSM, this point should be unreachable.
-        Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
-        response.recycle();
-    }
-
-    /** {@inheritDoc} */
-    protected  void setCellBroadcastConfig(int[] configValuesArray, Message response) {
-        // Unless CBS is implemented for GSM, this point should be unreachable.
-        Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
-        response.recycle();
-    }
-
     private int resultToCause(int rc) {
         switch (rc) {
             case Activity.RESULT_OK:
@@ -489,9 +470,10 @@
     }
 
     // This map holds incomplete concatenated messages waiting for assembly
-    private HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
+    private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
             new HashMap<SmsCbConcatInfo, byte[][]>();
 
+    @Override
     protected void handleBroadcastSms(AsyncResult ar) {
         try {
             byte[][] pdus = null;
@@ -503,9 +485,9 @@
                     for (int j = i; j < i + 8 && j < receivedPdu.length; j++) {
                         int b = receivedPdu[j] & 0xff;
                         if (b < 0x10) {
-                            sb.append("0");
+                            sb.append('0');
                         }
-                        sb.append(Integer.toHexString(b)).append(" ");
+                        sb.append(Integer.toHexString(b)).append(' ');
                     }
                     Log.d(TAG, sb.toString());
                 }
@@ -550,7 +532,8 @@
                 pdus[0] = receivedPdu;
             }
 
-            dispatchBroadcastPdus(pdus);
+            boolean isEmergencyMessage = SmsCbHeader.isEmergencyMessage(header.messageIdentifier);
+            dispatchBroadcastPdus(pdus, isEmergencyMessage);
 
             // Remove messages that are out of scope to prevent the map from
             // growing indefinitely, containing incomplete messages that were
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
index e55596b..6e87f21 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -27,6 +27,7 @@
 import com.android.internal.telephony.IccConstants;
 import com.android.internal.telephony.IccSmsInterfaceManager;
 import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.IntRangeManager;
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsRawData;
 
@@ -53,6 +54,9 @@
     private HashMap<Integer, HashSet<String>> mCellBroadcastSubscriptions =
             new HashMap<Integer, HashSet<String>>();
 
+    private CellBroadcastRangeManager mCellBroadcastRangeManager =
+            new CellBroadcastRangeManager();
+
     private static final int EVENT_LOAD_DONE = 1;
     private static final int EVENT_UPDATE_DONE = 2;
     private static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
@@ -212,7 +216,15 @@
     }
 
     public boolean enableCellBroadcast(int messageIdentifier) {
-        if (DBG) log("enableCellBroadcast");
+        return enableCellBroadcastRange(messageIdentifier, messageIdentifier);
+    }
+
+    public boolean disableCellBroadcast(int messageIdentifier) {
+        return disableCellBroadcastRange(messageIdentifier, messageIdentifier);
+    }
+
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+        if (DBG) log("enableCellBroadcastRange");
 
         Context context = mPhone.getContext();
 
@@ -222,30 +234,22 @@
 
         String client = context.getPackageManager().getNameForUid(
                 Binder.getCallingUid());
-        HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier);
 
-        if (clients == null) {
-            // This is a new message identifier
-            clients = new HashSet<String>();
-            mCellBroadcastSubscriptions.put(messageIdentifier, clients);
-
-            if (!updateCellBroadcastConfig()) {
-                mCellBroadcastSubscriptions.remove(messageIdentifier);
-                return false;
-            }
+        if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
+            log("Failed to add cell broadcast subscription for MID range " + startMessageId
+                    + " to " + endMessageId + " from client " + client);
+            return false;
         }
 
-        clients.add(client);
-
         if (DBG)
-            log("Added cell broadcast subscription for MID " + messageIdentifier
-                    + " from client " + client);
+            log("Added cell broadcast subscription for MID range " + startMessageId
+                    + " to " + endMessageId + " from client " + client);
 
         return true;
     }
 
-    public boolean disableCellBroadcast(int messageIdentifier) {
-        if (DBG) log("disableCellBroadcast");
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+        if (DBG) log("disableCellBroadcastRange");
 
         Context context = mPhone.getContext();
 
@@ -255,39 +259,56 @@
 
         String client = context.getPackageManager().getNameForUid(
                 Binder.getCallingUid());
-        HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier);
 
-        if (clients != null && clients.remove(client)) {
-            if (DBG)
-                log("Removed cell broadcast subscription for MID " + messageIdentifier
-                        + " from client " + client);
-
-            if (clients.isEmpty()) {
-                mCellBroadcastSubscriptions.remove(messageIdentifier);
-                updateCellBroadcastConfig();
-            }
-            return true;
+        if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
+            log("Failed to remove cell broadcast subscription for MID range " + startMessageId
+                    + " to " + endMessageId + " from client " + client);
+            return false;
         }
 
-        return false;
+        if (DBG)
+            log("Removed cell broadcast subscription for MID range " + startMessageId
+                    + " to " + endMessageId + " from client " + client);
+
+        return true;
     }
 
-    private boolean updateCellBroadcastConfig() {
-        Set<Integer> messageIdentifiers = mCellBroadcastSubscriptions.keySet();
+    class CellBroadcastRangeManager extends IntRangeManager {
+        private ArrayList<SmsBroadcastConfigInfo> mConfigList =
+                new ArrayList<SmsBroadcastConfigInfo>();
 
-        if (messageIdentifiers.size() > 0) {
-            SmsBroadcastConfigInfo[] configs =
-                    new SmsBroadcastConfigInfo[messageIdentifiers.size()];
-            int i = 0;
+        /**
+         * Called when the list of enabled ranges has changed. This will be
+         * followed by zero or more calls to {@link #addRange} followed by
+         * a call to {@link #finishUpdate}.
+         */
+        protected void startUpdate() {
+            mConfigList.clear();
+        }
 
-            for (int messageIdentifier : messageIdentifiers) {
-                configs[i++] = new SmsBroadcastConfigInfo(messageIdentifier, messageIdentifier,
-                        SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, true);
+        /**
+         * Called after {@link #startUpdate} to indicate a range of enabled
+         * values.
+         * @param startId the first id included in the range
+         * @param endId the last id included in the range
+         */
+        protected void addRange(int startId, int endId, boolean selected) {
+            mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
+                        SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
+        }
+
+        /**
+         * Called to indicate the end of a range update started by the
+         * previous call to {@link #startUpdate}.
+         */
+        protected boolean finishUpdate() {
+            if (mConfigList.isEmpty()) {
+                return setCellBroadcastActivation(false);
+            } else {
+                SmsBroadcastConfigInfo[] configs =
+                        mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]);
+                return setCellBroadcastConfig(configs) && setCellBroadcastActivation(true);
             }
-
-            return setCellBroadcastConfig(configs) && setCellBroadcastActivation(true);
-        } else {
-            return setCellBroadcastActivation(false);
         }
     }
 
@@ -313,7 +334,7 @@
 
     private boolean setCellBroadcastActivation(boolean activate) {
         if (DBG)
-            log("Calling setCellBroadcastActivation(" + activate + ")");
+            log("Calling setCellBroadcastActivation(" + activate + ')');
 
         synchronized (mLock) {
             Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
@@ -331,6 +352,7 @@
         return mSuccess;
     }
 
+    @Override
     protected void log(String msg) {
         Log.d(LOG_TAG, "[SimSmsInterfaceManager] " + msg);
     }
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
index 45f50bc..66e7ce0 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
@@ -30,11 +30,11 @@
  * and 9.4.4.2.3 for UMTS.
  * All other values can be treated as empty CBM data coding scheme.
  *
- * selected false means message types specified in <fromServiceId, toServiceId>
- * and <fromCodeScheme, toCodeScheme>are not accepted, while true means accepted.
+ * selected false means message types specified in {@code <fromServiceId, toServiceId>}
+ * and {@code <fromCodeScheme, toCodeScheme>} are not accepted, while true means accepted.
  *
  */
-public class SmsBroadcastConfigInfo {
+public final class SmsBroadcastConfigInfo {
     private int fromServiceId;
     private int toServiceId;
     private int fromCodeScheme;
@@ -46,11 +46,11 @@
      */
     public SmsBroadcastConfigInfo(int fromId, int toId, int fromScheme,
             int toScheme, boolean selected) {
-        setFromServiceId(fromId);
-        setToServiceId(toId);
-        setFromCodeScheme(fromScheme);
-        setToCodeScheme(toScheme);
-        this.setSelected(selected);
+        fromServiceId = fromId;
+        toServiceId = toId;
+        fromCodeScheme = fromScheme;
+        toCodeScheme = toScheme;
+        this.selected = selected;
     }
 
     /**
@@ -126,8 +126,8 @@
     @Override
     public String toString() {
         return "SmsBroadcastConfigInfo: Id [" +
-            getFromServiceId() + "," + getToServiceId() + "] Code [" +
-            getFromCodeScheme() + "," + getToCodeScheme() + "] " +
-            (isSelected() ? "ENABLED" : "DISABLED");
+                fromServiceId + ',' + toServiceId + "] Code [" +
+                fromCodeScheme + ',' + toCodeScheme + "] " +
+            (selected ? "ENABLED" : "DISABLED");
     }
-}
\ No newline at end of file
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
index 0945a38..8e6b79b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
@@ -16,7 +16,9 @@
 
 package com.android.internal.telephony.gsm;
 
-public class SmsCbHeader {
+import android.telephony.SmsCbConstants;
+
+public class SmsCbHeader implements SmsCbConstants {
     /**
      * Length of SMS-CB header
      */
@@ -33,6 +35,11 @@
     public static final int FORMAT_UMTS = 2;
 
     /**
+     * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
+     */
+    public static final int FORMAT_ETWS_PRIMARY = 3;
+
+    /**
      * Message type value as defined in 3gpp TS 25.324, section 11.1.
      */
     private static final int MESSAGE_TYPE_CBS_MESSAGE = 1;
@@ -40,7 +47,12 @@
     /**
      * Length of GSM pdus
      */
-    private static final int PDU_LENGTH_GSM = 88;
+    public static final int PDU_LENGTH_GSM = 88;
+
+    /**
+     * Maximum length of ETWS primary message GSM pdus
+     */
+    public static final int PDU_LENGTH_ETWS = 56;
 
     public final int geographicalScope;
 
@@ -58,12 +70,30 @@
 
     public final int format;
 
+    public final boolean etwsEmergencyUserAlert;
+
+    public final boolean etwsPopup;
+
+    public final int etwsWarningType;
+
     public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
         if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
             throw new IllegalArgumentException("Illegal PDU");
         }
 
-        if (pdu.length <= PDU_LENGTH_GSM) {
+        if (pdu.length <= PDU_LENGTH_ETWS) {
+            format = FORMAT_ETWS_PRIMARY;
+            geographicalScope = -1; //not applicable
+            messageCode = -1;
+            updateNumber = -1;
+            messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
+            dataCodingScheme = -1;
+            pageIndex = -1;
+            nrOfPages = -1;
+            etwsEmergencyUserAlert = (pdu[4] & 0x1) != 0;
+            etwsPopup = (pdu[5] & 0x80) != 0;
+            etwsWarningType = (pdu[4] & 0xfe) >> 1;
+        } else if (pdu.length <= PDU_LENGTH_GSM) {
             // GSM pdus are no more than 88 bytes
             format = FORMAT_GSM;
             geographicalScope = (pdu[0] & 0xc0) >> 6;
@@ -83,6 +113,9 @@
 
             this.pageIndex = pageIndex;
             this.nrOfPages = nrOfPages;
+            etwsEmergencyUserAlert = false;
+            etwsPopup = false;
+            etwsWarningType = -1;
         } else {
             // UMTS pdus are always at least 90 bytes since the payload includes
             // a number-of-pages octet and also one length octet per page
@@ -105,6 +138,77 @@
             // actual payload may contain several pages.
             pageIndex = 1;
             nrOfPages = 1;
+            etwsEmergencyUserAlert = false;
+            etwsPopup = false;
+            etwsWarningType = -1;
         }
     }
+
+    /**
+     * Return whether the specified message ID is an emergency (PWS) message type.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
+     * @param id the message identifier to check
+     * @return true if the message is emergency type; false otherwise
+     */
+    public static boolean isEmergencyMessage(int id) {
+        return id >= MESSAGE_ID_PWS_FIRST_IDENTIFIER && id <= MESSAGE_ID_PWS_LAST_IDENTIFIER;
+    }
+
+    /**
+     * Return whether the specified message ID is an ETWS emergency message type.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
+     * @param id the message identifier to check
+     * @return true if the message is ETWS emergency type; false otherwise
+     */
+    public static boolean isEtwsMessage(int id) {
+        return (id & MESSAGE_ID_ETWS_TYPE_MASK) == MESSAGE_ID_ETWS_TYPE;
+    }
+
+    /**
+     * Return whether the specified message ID is a CMAS emergency message type.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
+     * @param id the message identifier to check
+     * @return true if the message is CMAS emergency type; false otherwise
+     */
+    public static boolean isCmasMessage(int id) {
+        return id >= MESSAGE_ID_CMAS_FIRST_IDENTIFIER && id <= MESSAGE_ID_CMAS_LAST_IDENTIFIER;
+    }
+
+    /**
+     * Return whether the specified message code indicates an ETWS popup alert.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @param messageCode the message code to check
+     * @return true if the message code indicates a popup alert should be displayed
+     */
+    public static boolean isEtwsPopupAlert(int messageCode) {
+        return (messageCode & MESSAGE_CODE_ETWS_ACTIVATE_POPUP) != 0;
+    }
+
+    /**
+     * Return whether the specified message code indicates an ETWS emergency user alert.
+     * This method is static and takes an argument so that it can be used by
+     * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @param messageCode the message code to check
+     * @return true if the message code indicates an emergency user alert
+     */
+    public static boolean isEtwsEmergencyUserAlert(int messageCode) {
+        return (messageCode & MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT) != 0;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbHeader{GS=" + geographicalScope + ", messageCode=0x" +
+                Integer.toHexString(messageCode) + ", updateNumber=" + updateNumber +
+                ", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) +
+                ", DCS=0x" + Integer.toHexString(dataCodingScheme) +
+                ", page " + pageIndex + " of " + nrOfPages + '}';
+    }
 }
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java
index b131a01..417aac4 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java
@@ -18,6 +18,7 @@
 
 import android.telephony.SmsCbMessage;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 /**
  * Test cases for basic SmsCbMessage operations
@@ -663,4 +664,49 @@
 
         assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber());
     }
+
+    /* ETWS Test message including header */
+    private static final byte[] etwsMessageNormal = IccUtils.hexStringToBytes("000011001101" +
+            "0D0A5BAE57CE770C531790E85C716CBF3044573065B930675730" +
+            "9707767A751F30025F37304463FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" +
+            "0000000000000000000000000000");
+
+    private static final byte[] etwsMessageCancel = IccUtils.hexStringToBytes("000011001101" +
+            "0D0A5148307B3069002800310030003A0035" +
+            "00320029306E7DCA602557309707901F5831309253D66D883057307E3059FF086C178C615E81FF09" +
+            "00000000000000000000000000000000000000000000");
+
+    private static final byte[] etwsMessageTest = IccUtils.hexStringToBytes("000011031101" +
+            "0D0A5BAE57CE770C531790E85C716CBF3044" +
+            "573065B9306757309707300263FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" +
+            "00000000000000000000000000000000000000000000");
+
+    // FIXME: add example of ETWS primary notification PDU
+
+    public void testEtwsMessageNormal() {
+        SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageNormal);
+        Log.d("GsmSmsCbTest", msg.toString());
+        assertEquals("GS mismatch", 0, msg.getGeographicalScope());
+        assertEquals("message code mismatch", 0, msg.getMessageCode());
+        assertEquals("update number mismatch", 0, msg.getUpdateNumber());
+        assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier());
+    }
+
+    public void testEtwsMessageCancel() {
+        SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageCancel);
+        Log.d("GsmSmsCbTest", msg.toString());
+        assertEquals("GS mismatch", 0, msg.getGeographicalScope());
+        assertEquals("message code mismatch", 0, msg.getMessageCode());
+        assertEquals("update number mismatch", 0, msg.getUpdateNumber());
+        assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier());
+    }
+
+    public void testEtwsMessageTest() {
+        SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageTest);
+        Log.d("GsmSmsCbTest", msg.toString());
+        assertEquals("GS mismatch", 0, msg.getGeographicalScope());
+        assertEquals("message code mismatch", 0, msg.getMessageCode());
+        assertEquals("update number mismatch", 0, msg.getUpdateNumber());
+        assertEquals("message ID mismatch", 0x1103, msg.getMessageIdentifier());
+    }
 }
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java
new file mode 100644
index 0000000..79dca39
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2011 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.telephony;
+
+import android.test.AndroidTestCase;
+
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Test cases for the IntRangeManager class.
+ */
+public class IntRangeManagerTest extends AndroidTestCase {
+
+    private static final int SMS_CB_CODE_SCHEME_MIN = 0;
+    private static final int SMS_CB_CODE_SCHEME_MAX = 255;
+
+    private static final int FLAG_START_UPDATE_CALLED   = 0x01;
+    private static final int FLAG_ADD_RANGE_CALLED      = 0x02;
+    private static final int FLAG_FINISH_UPDATE_CALLED  = 0x04;
+
+    private static final int ALL_FLAGS_SET = FLAG_START_UPDATE_CALLED | FLAG_ADD_RANGE_CALLED |
+            FLAG_FINISH_UPDATE_CALLED;
+
+    /** Dummy IntRangeManager for testing. */
+    class TestIntRangeManager extends IntRangeManager {
+        ArrayList<SmsBroadcastConfigInfo> mConfigList =
+                new ArrayList<SmsBroadcastConfigInfo>();
+
+        int flags;
+        boolean finishUpdateReturnValue = true;
+
+        /**
+         * Called when the list of enabled ranges has changed. This will be
+         * followed by zero or more calls to {@link #addRange} followed by
+         * a call to {@link #finishUpdate}.
+         */
+        protected void startUpdate() {
+            mConfigList.clear();
+            flags |= FLAG_START_UPDATE_CALLED;
+        }
+
+        /**
+         * Called after {@link #startUpdate} to indicate a range of enabled
+         * values.
+         * @param startId the first id included in the range
+         * @param endId the last id included in the range
+         */
+        protected void addRange(int startId, int endId, boolean selected) {
+            mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
+                        SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
+            flags |= FLAG_ADD_RANGE_CALLED;
+        }
+
+        /**
+         * Called to indicate the end of a range update started by the
+         * previous call to {@link #startUpdate}.
+         */
+        protected boolean finishUpdate() {
+            flags |= FLAG_FINISH_UPDATE_CALLED;
+            return finishUpdateReturnValue;
+        }
+
+        /** Reset the object for the next test case. */
+        void reset() {
+            flags = 0;
+            mConfigList.clear();
+        }
+    }
+
+    public void testEmptyRangeManager() {
+        TestIntRangeManager testManager = new TestIntRangeManager();
+        assertEquals("expecting empty configlist", 0, testManager.mConfigList.size());
+    }
+
+    private void checkConfigInfo(SmsBroadcastConfigInfo info, int fromServiceId,
+            int toServiceId, int fromCodeScheme, int toCodeScheme, boolean selected) {
+        assertEquals("fromServiceId", fromServiceId, info.getFromServiceId());
+        assertEquals("toServiceId", toServiceId, info.getToServiceId());
+        assertEquals("fromCodeScheme", fromCodeScheme, info.getFromCodeScheme());
+        assertEquals("toCodeScheme", toCodeScheme, info.getToCodeScheme());
+        assertEquals("selected", selected, info.isSelected());
+    }
+
+    public void testAddSingleChannel() {
+        TestIntRangeManager testManager = new TestIntRangeManager();
+        assertEquals("flags before test", 0, testManager.flags);
+        assertTrue("enabling range", testManager.enableRange(123, 123, "client1"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 123, 123, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 123, 123, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+    }
+
+    public void testRemoveSingleChannel() {
+        TestIntRangeManager testManager = new TestIntRangeManager();
+        assertTrue("enabling range", testManager.enableRange(123, 123, "client1"));
+        assertEquals("flags after enable", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        testManager.reset();
+        assertTrue("disabling range", testManager.disableRange(123, 123, "client1"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 123, 123, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", FLAG_START_UPDATE_CALLED | FLAG_FINISH_UPDATE_CALLED,
+                testManager.flags);
+        assertEquals("configlist size", 0, testManager.mConfigList.size());
+    }
+
+    public void testRemoveBadChannel() {
+        TestIntRangeManager testManager = new TestIntRangeManager();
+        assertFalse("disabling missing range", testManager.disableRange(123, 123, "client1"));
+        assertEquals("flags after test", 0, testManager.flags);
+        assertEquals("configlist size", 0, testManager.mConfigList.size());
+    }
+
+    public void testAddTwoChannels() {
+        TestIntRangeManager testManager = new TestIntRangeManager();
+        assertEquals("flags before test", 0, testManager.flags);
+        assertTrue("enabling range 1", testManager.enableRange(100, 120, "client1"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 100, 120, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("enabling range 2", testManager.enableRange(200, 250, "client2"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 200, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 2, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 100, 120, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(1), 200, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+    }
+
+    public void testOverlappingChannels() {
+        TestIntRangeManager testManager = new TestIntRangeManager();
+        assertEquals("flags before test", 0, testManager.flags);
+        assertTrue("enabling range 1", testManager.enableRange(100, 200, "client1"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("enabling range 2", testManager.enableRange(150, 250, "client2"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 201, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 100, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("disabling range 1", testManager.disableRange(100, 200, "client1"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 100, 149, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("disabling range 2", testManager.disableRange(150, 250, "client2"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", FLAG_START_UPDATE_CALLED | FLAG_FINISH_UPDATE_CALLED,
+                testManager.flags);
+        assertEquals("configlist size", 0, testManager.mConfigList.size());
+    }
+
+    public void testOverlappingChannels2() {
+        TestIntRangeManager testManager = new TestIntRangeManager();
+        assertEquals("flags before test", 0, testManager.flags);
+        assertTrue("enabling range 1", testManager.enableRange(100, 200, "client1"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("enabling range 2", testManager.enableRange(150, 250, "client2"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 201, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("disabling range 2", testManager.disableRange(150, 250, "client2"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 201, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("disabling range 1", testManager.disableRange(100, 200, "client1"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+    }
+
+    public void testMultipleOverlappingChannels() {
+        TestIntRangeManager testManager = new TestIntRangeManager();
+        assertEquals("flags before test", 0, testManager.flags);
+        assertTrue("enabling range 1", testManager.enableRange(67, 9999, "client1"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 67, 9999, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("enabling range 2", testManager.enableRange(150, 250, "client2"));
+        assertEquals("flags after test", 0, testManager.flags);
+        assertEquals("configlist size", 0, testManager.mConfigList.size());
+        testManager.reset();
+        assertTrue("enabling range 3", testManager.enableRange(25, 75, "client3"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 25, 66, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("enabling range 4", testManager.enableRange(12, 500, "client4"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 12, 24, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("enabling range 5", testManager.enableRange(8000, 9998, "client5"));
+        assertEquals("flags after test", 0, testManager.flags);
+        assertEquals("configlist size", 0, testManager.mConfigList.size());
+        testManager.reset();
+        assertTrue("enabling range 6", testManager.enableRange(50000, 65535, "client6"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 2, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 12, 9999, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(1), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("disabling range 1", testManager.disableRange(67, 9999, "client1"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 2, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 501, 7999, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        checkConfigInfo(testManager.mConfigList.get(1), 9999, 9999, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 3, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 12, 500, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(1), 8000, 9998, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(2), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("disabling range 4", testManager.disableRange(12, 500, "client4"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 3, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 12, 24, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        checkConfigInfo(testManager.mConfigList.get(1), 76, 149, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        checkConfigInfo(testManager.mConfigList.get(2), 251, 500, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 4, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(1), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(2), 8000, 9998, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(3), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("disabling range 5", testManager.disableRange(8000, 9998, "client5"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 8000, 9998, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 3, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(1), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(2), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("disabling range 6", testManager.disableRange(50000, 65535, "client6"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 50000, 65535, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 2, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        checkConfigInfo(testManager.mConfigList.get(1), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("disabling range 2", testManager.disableRange(150, 250, "client2"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 150, 250, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, true);
+        testManager.reset();
+        assertTrue("disabling range 3", testManager.disableRange(25, 75, "client3"));
+        assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags);
+        assertEquals("configlist size", 1, testManager.mConfigList.size());
+        checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN,
+                SMS_CB_CODE_SCHEME_MAX, false);
+        testManager.reset();
+        assertTrue("updating ranges", testManager.updateRanges());
+        assertEquals("flags after test", FLAG_START_UPDATE_CALLED | FLAG_FINISH_UPDATE_CALLED,
+                testManager.flags);
+        assertEquals("configlist size", 0, testManager.mConfigList.size());
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 49c1e4b..5623526 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -443,7 +443,7 @@
         AffineTransform matrixTx = matrixDelegate.getAffineTransform();
 
         // combine them so that the given matrix is applied after.
-        currentTx.preConcatenate(matrixTx);
+        currentTx.concatenate(matrixTx);
 
         // give it to the graphics2D as a new matrix replacing all previous transform
         snapshot.setTransform(currentTx);
@@ -735,7 +735,7 @@
     /*package*/ static void native_drawCircle(int nativeCanvas,
             float cx, float cy, float radius, int paint) {
         native_drawOval(nativeCanvas,
-                new RectF(cx - radius, cy - radius, radius*2, radius*2),
+                new RectF(cx - radius, cy - radius, radius, radius),
                 paint);
     }
 
diff --git a/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java
new file mode 100644
index 0000000..afbe97c0
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 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.os;
+
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Delegate overriding selected methods of android.os.HandlerThread
+ *
+ * Through the layoutlib_create tool, selected methods of Handler have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class HandlerThread_Delegate {
+
+    private static Map<BridgeContext, List<HandlerThread>> sThreads =
+            new HashMap<BridgeContext, List<HandlerThread>>();
+
+    public static void cleanUp(BridgeContext context) {
+        List<HandlerThread> list = sThreads.get(context);
+        if (list != null) {
+            for (HandlerThread thread : list) {
+                thread.quit();
+            }
+
+            list.clear();
+            sThreads.remove(context);
+        }
+    }
+
+    // -------- Delegate methods
+
+    @LayoutlibDelegate
+    /*package*/ static void run(HandlerThread theThread) {
+        // record the thread so that it can be quit() on clean up.
+        BridgeContext context = RenderAction.getCurrentContext();
+        List<HandlerThread> list = sThreads.get(context);
+        if (list == null) {
+            list = new ArrayList<HandlerThread>();
+            sThreads.put(context, list);
+        }
+
+        list.add(theThread);
+
+        // ---- START DEFAULT IMPLEMENTATION.
+
+        theThread.mTid = Process.myTid();
+        Looper.prepare();
+        synchronized (theThread) {
+            theThread.mLooper = Looper.myLooper();
+            theThread.notifyAll();
+        }
+        Process.setThreadPriority(theThread.mPriority);
+        theThread.onLooperPrepared();
+        Looper.loop();
+        theThread.mTid = -1;
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
index d5266a5..ea7242c 100644
--- a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
@@ -43,6 +43,8 @@
  */
 public class LayoutInflater_Delegate {
 
+    public static boolean sIsInInclude = false;
+
     @LayoutlibDelegate
     /*package*/ static void parseInclude(LayoutInflater thisInflater,
             XmlPullParser parser, View parent, AttributeSet attrs)
@@ -109,10 +111,22 @@
                         // false means we need to rely on the included layout params.
                         ViewGroup.LayoutParams params = null;
                         try {
+                            // ---- START CHANGES
+                            sIsInInclude = true;
+                            // ---- END CHANGES
+
                             params = group.generateLayoutParams(attrs);
                         } catch (RuntimeException e) {
+                            // ---- START CHANGES
+                            sIsInInclude = false;
+                            // ---- END CHANGES
+
                             params = group.generateLayoutParams(childAttrs);
                         } finally {
+                            // ---- START CHANGES
+                            sIsInInclude = false;
+                            // ---- END CHANGES
+
                             if (params != null) {
                                 view.setLayoutParams(params);
                             }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index b1a7824..c74eb33 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -25,6 +25,7 @@
 import com.android.ide.common.rendering.api.RenderSession;
 import com.android.ide.common.rendering.api.Result;
 import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.Result.Status;
 import com.android.layoutlib.bridge.android.BridgeAssetManager;
 import com.android.layoutlib.bridge.impl.FontLoader;
 import com.android.layoutlib.bridge.impl.RenderDrawable;
@@ -39,6 +40,9 @@
 import android.graphics.Typeface;
 import android.graphics.Typeface_Delegate;
 import android.os.Looper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
 
 import java.io.File;
 import java.lang.ref.SoftReference;
@@ -192,10 +196,11 @@
                 Capability.UNBOUND_RENDERING,
                 Capability.CUSTOM_BACKGROUND_COLOR,
                 Capability.RENDER,
-                //Capability.LAYOUT_ONLY, // disable to run on ADT 10.0 which doesn't include this.
+                Capability.LAYOUT_ONLY,
                 Capability.EMBEDDED_LAYOUT,
-                Capability.VIEW_MANIPULATION);
-
+                Capability.VIEW_MANIPULATION,
+                Capability.ADAPTER_BINDING,
+                Capability.EXTENDED_VIEWINFO);
 
         BridgeAssetManager.initSystem();
 
@@ -367,6 +372,31 @@
         }
     }
 
+    @Override
+    public Result getViewParent(Object viewObject) {
+        if (viewObject instanceof View) {
+            return Status.SUCCESS.createResult(((View)viewObject).getParent());
+        }
+
+        throw new IllegalArgumentException("viewObject is not a View");
+    }
+
+    @Override
+    public Result getViewIndex(Object viewObject) {
+        if (viewObject instanceof View) {
+            View view = (View) viewObject;
+            ViewParent parentView = view.getParent();
+
+            if (parentView instanceof ViewGroup) {
+                Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
+            }
+
+            return Status.SUCCESS.createResult();
+        }
+
+        throw new IllegalArgumentException("viewObject is not a View");
+    }
+
     /**
      * Returns the lock for the bridge
      */
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index bf22c4d..e38b910 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -22,12 +22,10 @@
 import com.android.ide.common.rendering.api.RenderSession;
 import com.android.ide.common.rendering.api.Result;
 import com.android.ide.common.rendering.api.ViewInfo;
-import com.android.ide.common.rendering.api.Result.Status;
 import com.android.layoutlib.bridge.impl.RenderSessionImpl;
 
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewParent;
 
 import java.awt.image.BufferedImage;
 import java.util.List;
@@ -83,31 +81,6 @@
     }
 
     @Override
-    public Result getViewParent(Object viewObject) {
-        if (viewObject instanceof View) {
-            return Status.SUCCESS.createResult(((View)viewObject).getParent());
-        }
-
-        throw new IllegalArgumentException("viewObject is not a View");
-    }
-
-    @Override
-    public Result getViewIndex(Object viewObject) {
-        if (viewObject instanceof View) {
-            View view = (View) viewObject;
-            ViewParent parentView = view.getParent();
-
-            if (parentView instanceof ViewGroup) {
-                Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
-            }
-
-            return Status.SUCCESS.createResult();
-        }
-
-        throw new IllegalArgumentException("viewObject is not a View");
-    }
-
-    @Override
     public Result render(long timeout) {
         try {
             Bridge.prepareThread();
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
index 1ca3182..3d50b2a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -19,21 +19,23 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.widget.TextView;
 
 /**
  * Base class for mocked views.
- * 
+ *
  * TODO: implement onDraw and draw a rectangle in a random color with the name of the class
  * (or better the id of the view).
  */
 public class MockView extends TextView {
-    
+
     public MockView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        
+
         setText(this.getClass().getSimpleName());
         setTextColor(0xFF000000);
+        setGravity(Gravity.CENTER);
     }
 
     @Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index c4eee17..ea3d5d2 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -16,9 +16,11 @@
 
 package com.android.layoutlib.bridge.android;
 
+import com.android.ide.common.rendering.api.ILayoutPullParser;
 import com.android.ide.common.rendering.api.IProjectCallback;
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceReference;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.ide.common.rendering.api.StyleResourceValue;
 import com.android.layoutlib.bridge.Bridge;
@@ -27,6 +29,10 @@
 import com.android.resources.ResourceType;
 import com.android.util.Pair;
 
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -57,6 +63,7 @@
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -265,6 +272,107 @@
     }
 
 
+    public ResourceReference resolveId(int id) {
+        // first get the String related to this id in the framework
+        Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+
+        if (resourceInfo != null) {
+            return new ResourceReference(resourceInfo.getSecond(), true);
+        }
+
+        // didn't find a match in the framework? look in the project.
+        if (mProjectCallback != null) {
+            resourceInfo = mProjectCallback.resolveResourceId(id);
+
+            if (resourceInfo != null) {
+                return new ResourceReference(resourceInfo.getSecond(), false);
+            }
+        }
+
+        return null;
+    }
+
+    public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent,
+            boolean attachToRoot, boolean skipCallbackParser) {
+        String layoutName = resource.getName();
+        boolean isPlatformLayout = resource.isFramework();
+
+        if (isPlatformLayout == false && skipCallbackParser == false) {
+            // check if the project callback can provide us with a custom parser.
+            ILayoutPullParser parser = mProjectCallback.getParser(layoutName);
+            if (parser != null) {
+                BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
+                        this, resource.isFramework());
+                try {
+                    pushParser(blockParser);
+                    return Pair.of(
+                            mBridgeInflater.inflate(blockParser, parent, attachToRoot),
+                            true);
+                } finally {
+                    popParser();
+                }
+            }
+        }
+
+        ResourceValue resValue;
+        if (resource instanceof ResourceValue) {
+            resValue = (ResourceValue) resource;
+        } else {
+            if (isPlatformLayout) {
+                resValue = mRenderResources.getFrameworkResource(ResourceType.LAYOUT,
+                        resource.getName());
+            } else {
+                resValue = mRenderResources.getProjectResource(ResourceType.LAYOUT,
+                        resource.getName());
+            }
+        }
+
+        if (resValue != null) {
+
+            File xml = new File(resValue.getValue());
+            if (xml.isFile()) {
+                // we need to create a pull parser around the layout XML file, and then
+                // give that to our XmlBlockParser
+                try {
+                    KXmlParser parser = new KXmlParser();
+                    parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+                    parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$);
+
+                    // set the resource ref to have correct view cookies
+                    mBridgeInflater.setResourceReference(resource);
+
+                    BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
+                            this, resource.isFramework());
+                    try {
+                        pushParser(blockParser);
+                        return Pair.of(
+                                mBridgeInflater.inflate(blockParser, parent, attachToRoot),
+                                false);
+                    } finally {
+                        popParser();
+                    }
+                } catch (XmlPullParserException e) {
+                    Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                            "Failed to configure parser for " + xml, e, null /*data*/);
+                    // we'll return null below.
+                } catch (FileNotFoundException e) {
+                    // this shouldn't happen since we check above.
+                } finally {
+                    mBridgeInflater.setResourceReference(null);
+                }
+            } else {
+                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                        String.format("File %s is missing!", xml), null);
+            }
+        } else {
+            Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                    String.format("Layout %s%s does not exist.", isPlatformLayout ? "android:" : "",
+                            layoutName), null);
+        }
+
+        return Pair.of(null, false);
+    }
+
     // ------------- Activity Methods
 
     @Override
@@ -400,14 +508,13 @@
         BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length,
                 isPlatformFile);
 
-        // resolve the defStyleAttr value into a IStyleResourceValue
-        StyleResourceValue defStyleValues = null;
-
         // look for a custom style.
         String customStyle = null;
         if (set != null) {
             customStyle = set.getAttributeValue(null /* namespace*/, "style");
         }
+
+        StyleResourceValue customStyleValues = null;
         if (customStyle != null) {
             ResourceValue item = mRenderResources.findResValue(customStyle,
                     false /*forceFrameworkOnly*/);
@@ -416,75 +523,76 @@
             item = mRenderResources.resolveResValue(item);
 
             if (item instanceof StyleResourceValue) {
-                defStyleValues = (StyleResourceValue)item;
+                customStyleValues = (StyleResourceValue)item;
             }
         }
 
-        if (defStyleValues == null) {
-            if (defStyleAttr != 0) {
-                // get the name from the int.
-                String defStyleName = searchAttr(defStyleAttr);
+        // resolve the defStyleAttr value into a IStyleResourceValue
+        StyleResourceValue defStyleValues = null;
 
-                if (defaultPropMap != null) {
-                    defaultPropMap.put("style", defStyleName);
+        if (defStyleAttr != 0) {
+            // get the name from the int.
+            String defStyleName = searchAttr(defStyleAttr);
+
+            if (defaultPropMap != null) {
+                defaultPropMap.put("style", defStyleName);
+            }
+
+            // look for the style in the current theme, and its parent:
+            ResourceValue item = mRenderResources.findItemInTheme(defStyleName);
+
+            if (item != null) {
+                // item is a reference to a style entry. Search for it.
+                item = mRenderResources.findResValue(item.getValue(),
+                        false /*forceFrameworkOnly*/);
+
+                if (item instanceof StyleResourceValue) {
+                    defStyleValues = (StyleResourceValue)item;
                 }
+            } else {
+                Bridge.getLog().error(null,
+                        String.format(
+                                "Failed to find style '%s' in current theme", defStyleName),
+                        null /*data*/);
+            }
+        } else if (defStyleRes != 0) {
+            Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes);
+            if (value == null) {
+                value = mProjectCallback.resolveResourceId(defStyleRes);
+            }
 
-                // look for the style in the current theme, and its parent:
-                ResourceValue item = mRenderResources.findItemInTheme(defStyleName);
-
-                if (item != null) {
-                    // item is a reference to a style entry. Search for it.
-                    item = mRenderResources.findResValue(item.getValue(),
-                            false /*forceFrameworkOnly*/);
-
-                    if (item instanceof StyleResourceValue) {
-                        defStyleValues = (StyleResourceValue)item;
-                    }
-                } else {
-                    Bridge.getLog().error(null,
-                            String.format(
-                                    "Failed to find style '%s' in current theme", defStyleName),
-                            null /*data*/);
-                }
-            } else if (defStyleRes != 0) {
-                Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes);
-                if (value == null) {
-                    value = mProjectCallback.resolveResourceId(defStyleRes);
-                }
-
-                if (value != null) {
-                    if (value.getFirst() == ResourceType.STYLE) {
-                        // look for the style in the current theme, and its parent:
-                        ResourceValue item = mRenderResources.findItemInTheme(value.getSecond());
-                        if (item != null) {
-                            if (item instanceof StyleResourceValue) {
-                                if (defaultPropMap != null) {
-                                    defaultPropMap.put("style", item.getName());
-                                }
-
-                                defStyleValues = (StyleResourceValue)item;
+            if (value != null) {
+                if (value.getFirst() == ResourceType.STYLE) {
+                    // look for the style in the current theme, and its parent:
+                    ResourceValue item = mRenderResources.findItemInTheme(value.getSecond());
+                    if (item != null) {
+                        if (item instanceof StyleResourceValue) {
+                            if (defaultPropMap != null) {
+                                defaultPropMap.put("style", item.getName());
                             }
-                        } else {
-                            Bridge.getLog().error(null,
-                                    String.format(
-                                            "Style with id 0x%x (resolved to '%s') does not exist.",
-                                            defStyleRes, value.getSecond()),
-                                    null /*data*/);
+
+                            defStyleValues = (StyleResourceValue)item;
                         }
                     } else {
                         Bridge.getLog().error(null,
                                 String.format(
-                                        "Resouce id 0x%x is not of type STYLE (instead %s)",
-                                        defStyleRes, value.getFirst().toString()),
+                                        "Style with id 0x%x (resolved to '%s') does not exist.",
+                                        defStyleRes, value.getSecond()),
                                 null /*data*/);
                     }
                 } else {
                     Bridge.getLog().error(null,
                             String.format(
-                                    "Failed to find style with id 0x%x in current theme",
-                                    defStyleRes),
+                                    "Resouce id 0x%x is not of type STYLE (instead %s)",
+                                    defStyleRes, value.getFirst().toString()),
                             null /*data*/);
                 }
+            } else {
+                Bridge.getLog().error(null,
+                        String.format(
+                                "Failed to find style with id 0x%x in current theme",
+                                defStyleRes),
+                        null /*data*/);
             }
         }
 
@@ -509,8 +617,13 @@
                 if (value == null) {
                     ResourceValue resValue = null;
 
-                    // look for the value in the defStyle first (and its parent if needed)
-                    if (defStyleValues != null) {
+                    // look for the value in the custom style first (and its parent if needed)
+                    if (customStyleValues != null) {
+                        resValue = mRenderResources.findItemInStyle(customStyleValues, name);
+                    }
+
+                    // then look for the value in the default Style (and its parent if needed)
+                    if (resValue == null && defStyleValues != null) {
                         resValue = mRenderResources.findItemInStyle(defStyleValues, name);
                     }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java
index cb8d8dd..edfe83e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java
@@ -19,6 +19,7 @@
 import com.android.ide.common.rendering.api.IProjectCallback;
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.ide.common.rendering.api.MergeCookie;
+import com.android.ide.common.rendering.api.ResourceReference;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.layoutlib.bridge.Bridge;
 import com.android.resources.ResourceType;
@@ -35,7 +36,7 @@
 import android.view.ViewGroup;
 
 import java.io.File;
-import java.io.FileReader;
+import java.io.FileInputStream;
 
 /**
  * Custom implementation of {@link LayoutInflater} to handle custom views.
@@ -44,6 +45,7 @@
 
     private final IProjectCallback mProjectCallback;
     private boolean mIsInMerge = false;
+    private ResourceReference mResourceReference;
 
     /**
      * List of class prefixes which are tried first by default.
@@ -175,7 +177,7 @@
                     try {
                         KXmlParser parser = new KXmlParser();
                         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-                        parser.setInput(new FileReader(f));
+                        parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$
 
                         BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
                                 parser, bridgeContext, false);
@@ -223,23 +225,33 @@
                 // get the view key
                 Object viewKey = parser.getViewCookie();
 
-                // if there's no view key and the depth is 1 (ie this is the first tag), or 2
-                // (this is first item in included merge layout)
-                // look for a previous parser in the context, and check if this one has a viewkey.
-                int testDepth = mIsInMerge ? 2 : 1;
-                if (viewKey == null && parser.getDepth() == testDepth) {
+                if (viewKey == null) {
+                    int currentDepth = parser.getDepth();
+
+                    // test whether we are in an included file or in a adapter binding view.
                     BridgeXmlBlockParser previousParser = bc.getPreviousParser();
                     if (previousParser != null) {
-                        viewKey = previousParser.getViewCookie();
+                        // looks like we inside an embedded layout.
+                        // only apply the cookie of the calling node (<include>) if we are at the
+                        // top level of the embedded layout. If there is a merge tag, then
+                        // skip it and look for the 2nd level
+                        int testDepth = mIsInMerge ? 2 : 1;
+                        if (currentDepth == testDepth) {
+                            viewKey = previousParser.getViewCookie();
+                            // if we are in a merge, wrap the cookie in a MergeCookie.
+                            if (viewKey != null && mIsInMerge) {
+                                viewKey = new MergeCookie(viewKey);
+                            }
+                        }
+                    } else if (mResourceReference != null && currentDepth == 1) {
+                        // else if there's a resource reference, this means we are in an adapter
+                        // binding case. Set the resource ref as the view cookie only for the top
+                        // level view.
+                        viewKey = mResourceReference;
                     }
                 }
 
                 if (viewKey != null) {
-                    if (testDepth == 2) {
-                        // include-merge case
-                        viewKey = new MergeCookie(viewKey);
-                    }
-
                     bc.addViewKey(view, viewKey);
                 }
             }
@@ -250,6 +262,10 @@
         mIsInMerge = isInMerge;
     }
 
+    public void setResourceReference(ResourceReference reference) {
+        mResourceReference = reference;
+    }
+
     @Override
     public LayoutInflater cloneInContext(Context newContext) {
         return new BridgeInflater(this, newContext);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java
index 5e5aeb1..345f053 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java
@@ -46,7 +46,6 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.InputStream;
 
 /**
@@ -232,9 +231,8 @@
 
             try {
                 // check if the current parser can provide us with a custom parser.
-                BridgeXmlBlockParser currentParser = mContext.getCurrentParser();
-                if (currentParser != null) {
-                    parser = currentParser.getParser(value.getName());
+                if (mPlatformResourceFlag[0] == false) {
+                    parser = mProjectCallback.getParser(value.getName());
                 }
 
                 // create a new one manually if needed.
@@ -245,7 +243,7 @@
                         // give that to our XmlBlockParser
                         parser = new KXmlParser();
                         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-                        parser.setInput(new FileReader(xml));
+                        parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$);
                     }
                 }
 
@@ -283,7 +281,7 @@
                     // give that to our XmlBlockParser
                     parser = new KXmlParser();
                     parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-                    parser.setInput(new FileReader(xml));
+                    parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$);
 
                     return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
                 }
@@ -502,7 +500,7 @@
                     try {
                         KXmlParser parser = new KXmlParser();
                         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-                        parser.setInput(new FileReader(f));
+                        parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$);
 
                         return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
                     } catch (XmlPullParserException e) {
@@ -537,7 +535,7 @@
         try {
             KXmlParser parser = new KXmlParser();
             parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-            parser.setInput(new FileReader(f));
+            parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$);
 
             return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
         } catch (XmlPullParserException e) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java
index b9f769f..d4600a1 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java
@@ -36,10 +36,11 @@
 import android.graphics.drawable.Drawable;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
+import android.view.LayoutInflater_Delegate;
 import android.view.ViewGroup.LayoutParams;
 
 import java.io.File;
-import java.io.FileReader;
+import java.io.FileInputStream;
 import java.util.Arrays;
 import java.util.Map;
 
@@ -315,7 +316,7 @@
             try {
                 KXmlParser parser = new KXmlParser();
                 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-                parser.setInput(new FileReader(f));
+                parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$);
 
                 BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
                         parser, mContext, resValue.isFramework());
@@ -471,40 +472,23 @@
      */
     @Override
     public int getDimensionPixelSize(int index, int defValue) {
-        if (mResourceData[index] == null) {
+        try {
+            return getDimension(index);
+        } catch (RuntimeException e) {
+            if (mResourceData[index] != null) {
+                String s = mResourceData[index].getValue();
+
+                if (s != null) {
+                    // looks like we were unable to resolve the dimension value
+                    Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+                            String.format(
+                                "\"%1$s\" in attribute \"%2$s\" is not a valid format.",
+                                s, mNames[index]), null /*data*/);
+                }
+            }
+
             return defValue;
         }
-
-        String s = mResourceData[index].getValue();
-
-        if (s == null) {
-            return defValue;
-        } else if (s.equals(BridgeConstants.MATCH_PARENT) ||
-                s.equals(BridgeConstants.FILL_PARENT)) {
-            return LayoutParams.MATCH_PARENT;
-        } else if (s.equals(BridgeConstants.WRAP_CONTENT)) {
-            return LayoutParams.WRAP_CONTENT;
-        } else if (RenderResources.REFERENCE_NULL.equals(s)) {
-            return defValue;
-        }
-
-        if (ResourceHelper.stringToFloat(s, mValue)) {
-            float f = mValue.getDimension(mBridgeResources.mMetrics);
-
-            final int res = (int)(f+0.5f);
-            if (res != 0) return res;
-            if (f == 0) return 0;
-            if (f > 0) return 1;
-            return defValue; // this is basically unreachable.
-        }
-
-        // looks like we were unable to resolve the dimension value
-        Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
-                String.format(
-                    "\"%1$s\" in attribute \"%2$s\" is not a valid format.",
-                    s, mNames[index]), null /*data*/);
-
-        return defValue;
     }
 
     /**
@@ -521,7 +505,20 @@
      */
     @Override
     public int getLayoutDimension(int index, String name) {
-        return getDimensionPixelSize(index, 0);
+        try {
+            // this will throw an exception
+            return getDimension(index);
+        } catch (RuntimeException e) {
+
+            if (LayoutInflater_Delegate.sIsInInclude) {
+                throw new RuntimeException();
+            }
+
+            Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
+                    "You must supply a " + name + " attribute.", null);
+
+            return 0;
+        }
     }
 
     @Override
@@ -529,6 +526,36 @@
         return getDimensionPixelSize(index, defValue);
     }
 
+    private int getDimension(int index) {
+        if (mResourceData[index] == null) {
+            throw new RuntimeException();
+        }
+
+        String s = mResourceData[index].getValue();
+
+        if (s == null) {
+            throw new RuntimeException();
+        } else if (s.equals(BridgeConstants.MATCH_PARENT) ||
+                s.equals(BridgeConstants.FILL_PARENT)) {
+            return LayoutParams.MATCH_PARENT;
+        } else if (s.equals(BridgeConstants.WRAP_CONTENT)) {
+            return LayoutParams.WRAP_CONTENT;
+        } else if (RenderResources.REFERENCE_NULL.equals(s)) {
+            throw new RuntimeException();
+        }
+
+        if (ResourceHelper.stringToFloat(s, mValue)) {
+            float f = mValue.getDimension(mBridgeResources.mMetrics);
+
+            final int res = (int)(f+0.5f);
+            if (res != 0) return res;
+            if (f == 0) return 0;
+            if (f > 0) return 1;
+        }
+
+        throw new RuntimeException();
+    }
+
     /**
      * Retrieve a fractional unit attribute at <var>index</var>.
      *
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
index 2f54ae6..70dbaa4 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
@@ -69,14 +69,6 @@
         return mPlatformFile;
     }
 
-    public ILayoutPullParser getParser(String layoutName) {
-        if (mParser instanceof ILayoutPullParser) {
-            return ((ILayoutPullParser)mParser).getParser(layoutName);
-        }
-
-        return null;
-    }
-
     public Object getViewCookie() {
         if (mParser instanceof ILayoutPullParser) {
             return ((ILayoutPullParser)mParser).getViewCookie();
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
index 0c4b0d3..060e6ee 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -73,7 +73,7 @@
         parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
         parser.setInput(
                 getClass().getResourceAsStream(layoutPath),
-                "UTF8");
+                "UTF8"); //$NON-NLS-1$
 
         BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
                 parser, (BridgeContext) context, false /*platformFile*/);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index 8e80c21..6194f5d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -29,6 +29,7 @@
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.resources.ResourceType;
 
+import android.os.HandlerThread_Delegate;
 import android.util.DisplayMetrics;
 
 import java.util.concurrent.TimeUnit;
@@ -228,6 +229,10 @@
     private void tearDown() {
         // Make sure to remove static references, otherwise we could not unload the lib
         mContext.disposeResources();
+
+        // quit HandlerThread created during this session.
+        HandlerThread_Delegate.cleanUp(sCurrentContext);
+
         sCurrentContext = null;
 
         Bridge.setLog(null);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 348c579..f29a9d0 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -22,12 +22,14 @@
 import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
 
+import com.android.ide.common.rendering.api.AdapterBinding;
 import com.android.ide.common.rendering.api.IAnimationListener;
 import com.android.ide.common.rendering.api.ILayoutPullParser;
 import com.android.ide.common.rendering.api.IProjectCallback;
 import com.android.ide.common.rendering.api.RenderParams;
 import com.android.ide.common.rendering.api.RenderResources;
 import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.ResourceReference;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.ide.common.rendering.api.Result;
 import com.android.ide.common.rendering.api.SessionParams;
@@ -44,6 +46,8 @@
 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
 import com.android.layoutlib.bridge.bars.PhoneSystemBar;
 import com.android.layoutlib.bridge.bars.TitleBar;
+import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
+import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
 import com.android.resources.ResourceType;
 import com.android.resources.ScreenSize;
 import com.android.util.Pair;
@@ -62,8 +66,14 @@
 import android.view.View.AttachInfo;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.AbsListView;
+import android.widget.AbsSpinner;
+import android.widget.AdapterView;
+import android.widget.ExpandableListView;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
+import android.widget.ListView;
 import android.widget.TabHost;
 import android.widget.TabWidget;
 import android.widget.TabHost.TabSpec;
@@ -268,6 +278,9 @@
 
             View view = mInflater.inflate(mBlockParser, mContentRoot);
 
+            // done with the parser, pop it.
+            context.popParser();
+
             // set the AttachInfo on the root view.
             AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(),
                     new Handler(), null);
@@ -453,7 +466,7 @@
                 mViewRoot.draw(mCanvas);
             }
 
-            mViewInfoList = startVisitingViews(mViewRoot, 0);
+            mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode());
 
             // success!
             return SUCCESS.createResult();
@@ -875,6 +888,75 @@
             throws PostInflateException {
         if (view instanceof TabHost) {
             setupTabHost((TabHost)view, projectCallback);
+        } else if (view instanceof AdapterView<?>) {
+            // get the view ID.
+            int id = view.getId();
+
+            BridgeContext context = getContext();
+
+            // get a ResourceReference from the integer ID.
+            ResourceReference listRef = context.resolveId(id);
+
+            if (listRef != null) {
+                SessionParams params = getParams();
+                AdapterBinding binding = params.getAdapterBindings().get(listRef);
+
+                // if there was no adapter binding, trying to get it from the call back.
+                if (binding == null) {
+                    binding = params.getProjectCallback().getAdapterBinding(listRef,
+                            context.getViewKey(view), view);
+                }
+
+                if (binding != null) {
+
+                    if (view instanceof AbsListView) {
+                        if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
+                                view instanceof ListView) {
+                            ListView list = (ListView) view;
+
+                            boolean skipCallbackParser = false;
+
+                            int count = binding.getHeaderCount();
+                            for (int i = 0 ; i < count ; i++) {
+                                Pair<View, Boolean> pair = context.inflateView(
+                                        binding.getHeaderAt(i),
+                                        list, false /*attachToRoot*/, skipCallbackParser);
+                                if (pair.getFirst() != null) {
+                                    list.addHeaderView(pair.getFirst());
+                                }
+
+                                skipCallbackParser |= pair.getSecond();
+                            }
+
+                            count = binding.getFooterCount();
+                            for (int i = 0 ; i < count ; i++) {
+                                Pair<View, Boolean> pair = context.inflateView(
+                                        binding.getFooterAt(i),
+                                        list, false /*attachToRoot*/, skipCallbackParser);
+                                if (pair.getFirst() != null) {
+                                    list.addFooterView(pair.getFirst());
+                                }
+
+                                skipCallbackParser |= pair.getSecond();
+                            }
+                        }
+
+                        if (view instanceof ExpandableListView) {
+                            ((ExpandableListView) view).setAdapter(
+                                    new FakeExpandableAdapter(
+                                            listRef, binding, params.getProjectCallback()));
+                        } else {
+                            ((AbsListView) view).setAdapter(
+                                    new FakeAdapter(
+                                            listRef, binding, params.getProjectCallback()));
+                        }
+                    } else if (view instanceof AbsSpinner) {
+                        ((AbsSpinner) view).setAdapter(
+                                new FakeAdapter(
+                                        listRef, binding, params.getProjectCallback()));
+                    }
+                }
+            }
         } else if (view instanceof ViewGroup) {
             ViewGroup group = (ViewGroup)view;
             final int count = group.getChildCount();
@@ -959,7 +1041,7 @@
         }
     }
 
-    private List<ViewInfo> startVisitingViews(View view, int offset) {
+    private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) {
         if (view == null) {
             return null;
         }
@@ -968,7 +1050,7 @@
         offset += view.getTop();
 
         if (view == mContentRoot) {
-            return visitAllChildren(mContentRoot, offset);
+            return visitAllChildren(mContentRoot, offset, setExtendedInfo);
         }
 
         // otherwise, look for mContentRoot in the children
@@ -976,7 +1058,8 @@
             ViewGroup group = ((ViewGroup) view);
 
             for (int i = 0; i < group.getChildCount(); i++) {
-                List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset);
+                List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset,
+                        setExtendedInfo);
                 if (list != null) {
                     return list;
                 }
@@ -991,8 +1074,9 @@
      * bounds of all the views.
      * @param view the root View
      * @param offset an offset for the view bounds.
+     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
      */
-    private ViewInfo visit(View view, int offset) {
+    private ViewInfo visit(View view, int offset, boolean setExtendedInfo) {
         if (view == null) {
             return null;
         }
@@ -1002,9 +1086,22 @@
                 view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset,
                 view, view.getLayoutParams());
 
+        if (setExtendedInfo) {
+            MarginLayoutParams marginParams = null;
+            LayoutParams params = view.getLayoutParams();
+            if (params instanceof MarginLayoutParams) {
+                marginParams = (MarginLayoutParams) params;
+            }
+            result.setExtendedInfo(view.getBaseline(),
+                    marginParams != null ? marginParams.leftMargin : 0,
+                    marginParams != null ? marginParams.topMargin : 0,
+                    marginParams != null ? marginParams.rightMargin : 0,
+                    marginParams != null ? marginParams.bottomMargin : 0);
+        }
+
         if (view instanceof ViewGroup) {
             ViewGroup group = ((ViewGroup) view);
-            result.setChildren(visitAllChildren(group, 0 /*offset*/));
+            result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo));
         }
 
         return result;
@@ -1015,15 +1112,17 @@
      * containing the bounds of all the views.
      * @param view the root View
      * @param offset an offset for the view bounds.
+     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
      */
-    private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset) {
+    private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
+            boolean setExtendedInfo) {
         if (viewGroup == null) {
             return null;
         }
 
         List<ViewInfo> children = new ArrayList<ViewInfo>();
         for (int i = 0; i < viewGroup.getChildCount(); i++) {
-            children.add(visit(viewGroup.getChildAt(i), offset));
+            children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo));
         }
         return children;
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index 649160e..e5efa4e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -44,7 +44,6 @@
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
@@ -115,7 +114,7 @@
 
     public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) {
         String value = resValue.getValue();
-        if (value != null) {
+        if (value != null && RenderResources.REFERENCE_NULL.equals(value) == false) {
             // first check if the value is a file (xml most likely)
             File f = new File(value);
             if (f.isFile()) {
@@ -124,7 +123,7 @@
                     // providing an XmlPullParser
                     KXmlParser parser = new KXmlParser();
                     parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-                    parser.setInput(new FileReader(f));
+                    parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$);
 
                     BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
                             parser, context, resValue.isFramework());
@@ -206,7 +205,7 @@
                     // let the framework inflate the Drawable from the XML file.
                     KXmlParser parser = new KXmlParser();
                     parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-                    parser.setInput(new FileReader(f));
+                    parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$);
 
                     BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
                             parser, context, value.isFramework());
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java
new file mode 100644
index 0000000..e0414fe
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2011 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.layoutlib.bridge.impl.binding;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.DataBindingItem;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.util.Pair;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.Checkable;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Base adapter to do fake data binding in {@link AdapterView} objects.
+ */
+public class BaseAdapter {
+
+    /**
+     * This is the items provided by the adapter. They are dynamically generated.
+     */
+    protected final static class AdapterItem {
+        private final DataBindingItem mItem;
+        private final int mType;
+        private final int mFullPosition;
+        private final int mPositionPerType;
+        private List<AdapterItem> mChildren;
+
+        protected AdapterItem(DataBindingItem item, int type, int fullPosition,
+                int positionPerType) {
+            mItem = item;
+            mType = type;
+            mFullPosition = fullPosition;
+            mPositionPerType = positionPerType;
+        }
+
+        void addChild(AdapterItem child) {
+            if (mChildren == null) {
+                mChildren = new ArrayList<AdapterItem>();
+            }
+
+            mChildren.add(child);
+        }
+
+        List<AdapterItem> getChildren() {
+            if (mChildren != null) {
+                return mChildren;
+            }
+
+            return Collections.emptyList();
+        }
+
+        int getType() {
+            return mType;
+        }
+
+        int getFullPosition() {
+            return mFullPosition;
+        }
+
+        int getPositionPerType() {
+            return mPositionPerType;
+        }
+
+        DataBindingItem getDataBindingItem() {
+            return mItem;
+        }
+    }
+
+    private final AdapterBinding mBinding;
+    private final IProjectCallback mCallback;
+    private final ResourceReference mAdapterRef;
+    private boolean mSkipCallbackParser = false;
+
+    protected final List<AdapterItem> mItems = new ArrayList<AdapterItem>();
+
+    protected BaseAdapter(ResourceReference adapterRef, AdapterBinding binding,
+            IProjectCallback callback) {
+        mAdapterRef = adapterRef;
+        mBinding = binding;
+        mCallback = callback;
+    }
+
+    // ------- Some Adapter method used by all children classes.
+
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    public boolean isEmpty() {
+        return mItems.size() == 0;
+    }
+
+    public void registerDataSetObserver(DataSetObserver observer) {
+        // pass
+    }
+
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        // pass
+    }
+
+    // -------
+
+
+    protected AdapterBinding getBinding() {
+        return mBinding;
+    }
+
+    protected View getView(AdapterItem item, AdapterItem parentItem, View convertView,
+            ViewGroup parent) {
+        // we don't care about recycling here because we never scroll.
+        DataBindingItem dataBindingItem = item.getDataBindingItem();
+
+        BridgeContext context = RenderAction.getCurrentContext();
+
+        Pair<View, Boolean> pair = context.inflateView(dataBindingItem.getViewReference(),
+                parent, false /*attachToRoot*/, mSkipCallbackParser);
+
+        View view = pair.getFirst();
+        mSkipCallbackParser |= pair.getSecond();
+
+        if (view != null) {
+            fillView(context, view, item, parentItem);
+        } else {
+            // create a text view to display an error.
+            TextView tv = new TextView(context);
+            tv.setText("Unable to find layout: " + dataBindingItem.getViewReference().getName());
+            view = tv;
+        }
+
+        return view;
+    }
+
+    private void fillView(BridgeContext context, View view, AdapterItem item,
+            AdapterItem parentItem) {
+        if (view instanceof ViewGroup) {
+            ViewGroup group = (ViewGroup) view;
+            final int count = group.getChildCount();
+            for (int i = 0 ; i < count ; i++) {
+                fillView(context, group.getChildAt(i), item, parentItem);
+            }
+        } else {
+            int id = view.getId();
+            if (id != 0) {
+                ResourceReference resolvedRef = context.resolveId(id);
+                if (resolvedRef != null) {
+                    int fullPosition = item.getFullPosition();
+                    int positionPerType = item.getPositionPerType();
+                    int fullParentPosition = parentItem != null ? parentItem.getFullPosition() : 0;
+                    int parentPositionPerType = parentItem != null ?
+                            parentItem.getPositionPerType() : 0;
+
+                    if (view instanceof TextView) {
+                        TextView tv = (TextView) view;
+                        Object value = mCallback.getAdapterItemValue(
+                                mAdapterRef, context.getViewKey(view),
+                                item.getDataBindingItem().getViewReference(),
+                                fullPosition, positionPerType,
+                                fullParentPosition, parentPositionPerType,
+                                resolvedRef, ViewAttribute.TEXT, tv.getText().toString());
+                        if (value != null) {
+                            if (value.getClass() != ViewAttribute.TEXT.getAttributeClass()) {
+                                Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
+                                        "Wrong Adapter Item value class for TEXT. Expected String, got %s",
+                                        value.getClass().getName()), null);
+                            } else {
+                                tv.setText((String) value);
+                            }
+                        }
+                    }
+
+                    if (view instanceof Checkable) {
+                        Checkable cb = (Checkable) view;
+
+                        Object value = mCallback.getAdapterItemValue(
+                                mAdapterRef, context.getViewKey(view),
+                                item.getDataBindingItem().getViewReference(),
+                                fullPosition, positionPerType,
+                                fullParentPosition, parentPositionPerType,
+                                resolvedRef, ViewAttribute.IS_CHECKED, cb.isChecked());
+                        if (value != null) {
+                            if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) {
+                                Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
+                                        "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+                                        value.getClass().getName()), null);
+                            } else {
+                                cb.setChecked((Boolean) value);
+                            }
+                        }
+                    }
+
+                    if (view instanceof ImageView) {
+                        ImageView iv = (ImageView) view;
+
+                        Object value = mCallback.getAdapterItemValue(
+                                mAdapterRef, context.getViewKey(view),
+                                item.getDataBindingItem().getViewReference(),
+                                fullPosition, positionPerType,
+                                fullParentPosition, parentPositionPerType,
+                                resolvedRef, ViewAttribute.SRC, iv.getDrawable());
+                        if (value != null) {
+                            if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) {
+                                Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
+                                        "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+                                        value.getClass().getName()), null);
+                            } else {
+                                // FIXME
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java
new file mode 100644
index 0000000..c9bb424
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2011 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.layoutlib.bridge.impl.binding;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.DataBindingItem;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.ResourceReference;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.SpinnerAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fake adapter to do fake data binding in {@link AdapterView} objects for {@link ListAdapter}
+ * and {@link SpinnerAdapter}.
+ *
+ */
+public class FakeAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter {
+
+    // don't use a set because the order is important.
+    private final List<ResourceReference> mTypes = new ArrayList<ResourceReference>();
+
+    public FakeAdapter(ResourceReference adapterRef, AdapterBinding binding,
+            IProjectCallback callback) {
+        super(adapterRef, binding, callback);
+
+        final int repeatCount = getBinding().getRepeatCount();
+        final int itemCount = getBinding().getItemCount();
+
+        // Need an array to count for each type.
+        // This is likely too big, but is the max it can be.
+        int[] typeCount = new int[itemCount];
+
+        // We put several repeating sets.
+        for (int r = 0 ; r < repeatCount ; r++) {
+            // loop on the type of list items, and add however many for each type.
+            for (DataBindingItem dataBindingItem : getBinding()) {
+                ResourceReference viewRef = dataBindingItem.getViewReference();
+                int typeIndex = mTypes.indexOf(viewRef);
+                if (typeIndex == -1) {
+                    typeIndex = mTypes.size();
+                    mTypes.add(viewRef);
+                }
+
+                int count = dataBindingItem.getCount();
+
+                int index = typeCount[typeIndex];
+                typeCount[typeIndex] += count;
+
+                for (int k = 0 ; k < count ; k++) {
+                    mItems.add(new AdapterItem(dataBindingItem, typeIndex, mItems.size(), index++));
+                }
+            }
+        }
+    }
+
+    public boolean isEnabled(int position) {
+        return true;
+    }
+
+    public int getCount() {
+        return mItems.size();
+    }
+
+    public Object getItem(int position) {
+        return mItems.get(position);
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    public int getItemViewType(int position) {
+        return mItems.get(position).getType();
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        // we don't care about recycling here because we never scroll.
+        AdapterItem item = mItems.get(position);
+        return getView(item, null /*parentGroup*/, convertView, parent);
+    }
+
+    public int getViewTypeCount() {
+        return mTypes.size();
+    }
+
+    // ---- SpinnerAdapter
+
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+        // pass
+        return null;
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java
new file mode 100644
index 0000000..2c492e3
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2011 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.layoutlib.bridge.impl.binding;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.DataBindingItem;
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.ide.common.rendering.api.ResourceReference;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ExpandableListAdapter;
+import android.widget.HeterogeneousExpandableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FakeExpandableAdapter extends BaseAdapter implements ExpandableListAdapter,
+        HeterogeneousExpandableList {
+
+    // don't use a set because the order is important.
+    private final List<ResourceReference> mGroupTypes = new ArrayList<ResourceReference>();
+    private final List<ResourceReference> mChildrenTypes = new ArrayList<ResourceReference>();
+
+    public FakeExpandableAdapter(ResourceReference adapterRef, AdapterBinding binding,
+            IProjectCallback callback) {
+        super(adapterRef, binding, callback);
+
+        createItems(binding, binding.getItemCount(), binding.getRepeatCount(), mGroupTypes, 1);
+    }
+
+    private void createItems(Iterable<DataBindingItem> iterable, final int itemCount,
+            final int repeatCount, List<ResourceReference> types, int depth) {
+        // Need an array to count for each type.
+        // This is likely too big, but is the max it can be.
+        int[] typeCount = new int[itemCount];
+
+        // we put several repeating sets.
+        for (int r = 0 ; r < repeatCount ; r++) {
+            // loop on the type of list items, and add however many for each type.
+            for (DataBindingItem dataBindingItem : iterable) {
+                ResourceReference viewRef = dataBindingItem.getViewReference();
+                int typeIndex = types.indexOf(viewRef);
+                if (typeIndex == -1) {
+                    typeIndex = types.size();
+                    types.add(viewRef);
+                }
+
+                List<DataBindingItem> children = dataBindingItem.getChildren();
+                int count = dataBindingItem.getCount();
+
+                // if there are children, we use the count as a repeat count for the children.
+                if (children.size() > 0) {
+                    count = 1;
+                }
+
+                int index = typeCount[typeIndex];
+                typeCount[typeIndex] += count;
+
+                for (int k = 0 ; k < count ; k++) {
+                    AdapterItem item = new AdapterItem(dataBindingItem, typeIndex, mItems.size(),
+                            index++);
+                    mItems.add(item);
+
+                    if (children.size() > 0) {
+                        createItems(dataBindingItem, depth + 1);
+                    }
+                }
+            }
+        }
+    }
+
+    private void createItems(DataBindingItem item, int depth) {
+        if (depth == 2) {
+            createItems(item, item.getChildren().size(), item.getCount(), mChildrenTypes, depth);
+        }
+    }
+
+    private AdapterItem getChildItem(int groupPosition, int childPosition) {
+        AdapterItem item = mItems.get(groupPosition);
+
+        List<AdapterItem> children = item.getChildren();
+        return children.get(childPosition);
+    }
+
+    // ---- ExpandableListAdapter
+
+    public int getGroupCount() {
+        return mItems.size();
+    }
+
+    public int getChildrenCount(int groupPosition) {
+        AdapterItem item = mItems.get(groupPosition);
+        return item.getChildren().size();
+    }
+
+    public Object getGroup(int groupPosition) {
+        return mItems.get(groupPosition);
+    }
+
+    public Object getChild(int groupPosition, int childPosition) {
+        return getChildItem(groupPosition, childPosition);
+    }
+
+    public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+            ViewGroup parent) {
+        // we don't care about recycling here because we never scroll.
+        AdapterItem item = mItems.get(groupPosition);
+        return getView(item, null /*parentItem*/, convertView, parent);
+    }
+
+    public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+            View convertView, ViewGroup parent) {
+        // we don't care about recycling here because we never scroll.
+        AdapterItem parentItem = mItems.get(groupPosition);
+        AdapterItem item = getChildItem(groupPosition, childPosition);
+        return getView(item, parentItem, convertView, parent);
+    }
+
+    public long getGroupId(int groupPosition) {
+        return groupPosition;
+    }
+
+    public long getChildId(int groupPosition, int childPosition) {
+        return childPosition;
+    }
+
+    public long getCombinedGroupId(long groupId) {
+        return groupId << 16 | 0x0000FFFF;
+    }
+
+    public long getCombinedChildId(long groupId, long childId) {
+        return groupId << 16 | childId;
+    }
+
+    public boolean isChildSelectable(int groupPosition, int childPosition) {
+        return true;
+    }
+
+    public void onGroupCollapsed(int groupPosition) {
+        // pass
+    }
+
+    public void onGroupExpanded(int groupPosition) {
+        // pass
+    }
+
+    // ---- HeterogeneousExpandableList
+
+    public int getChildType(int groupPosition, int childPosition) {
+        return getChildItem(groupPosition, childPosition).getType();
+    }
+
+    public int getChildTypeCount() {
+        return mChildrenTypes.size();
+    }
+
+    public int getGroupType(int groupPosition) {
+        return mItems.get(groupPosition).getType();
+    }
+
+    public int getGroupTypeCount() {
+        return mGroupTypes.size();
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
index 3252fb4..70d5446 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
@@ -44,7 +44,7 @@
 
         InputStream input = this.getClass().getClassLoader().getResourceAsStream(
             "com/android/layoutlib/testdata/layout1.xml");
-        parser.setInput(input, null /*encoding*/);
+        parser.setInput(input, "UTF-8"); //$NON-NLS-1$
 
         assertEquals(XmlPullParser.START_DOCUMENT, parser.next());
 
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 85e6c3f..95506c6 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -100,11 +100,11 @@
         "android.content.res.Resources$Theme#resolveAttribute",
         "android.graphics.BitmapFactory#finishDecode",
         "android.os.Handler#sendMessageAtTime",
+        "android.os.HandlerThread#run",
         "android.os.Build#getString",
         "android.view.LayoutInflater#parseInclude",
         "android.view.View#isInEditMode",
         "com.android.internal.util.XmlUtils#convertValueToInt",
-        // TODO: comment out once DelegateClass is working
     };
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 90295e0..fedf3bb 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -166,6 +166,9 @@
      */
     private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
 
+    //Minimum dhcp lease duration for renewal
+    private static final int MIN_RENEWAL_TIME_SECS = 5 * 60; //5 minutes
+
     private static final int DRIVER_POWER_MODE_AUTO = 0;
     private static final int DRIVER_POWER_MODE_ACTIVE = 1;
 
@@ -2507,14 +2510,8 @@
                         if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {
                             event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
                             Log.d(TAG, "DHCP succeeded with lease: " + mDhcpInfo.leaseDuration);
-                            //Do it a bit earlier than half the lease duration time
-                            //to beat the native DHCP client and avoid extra packets
-                            //48% for one hour lease time = 29 minutes
-                            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                                    SystemClock.elapsedRealtime() +
-                                    mDhcpInfo.leaseDuration * 480, //in milliseconds
-                                    mDhcpRenewalIntent);
-                        } else {
+                            setDhcpRenewalAlarm(mDhcpInfo.leaseDuration);
+                       } else {
                             event = EVENT_INTERFACE_CONFIGURATION_FAILED;
                             Log.e(TAG, "DHCP request failed: " + NetworkUtils.getDhcpError());
                         }
@@ -2551,10 +2548,7 @@
                                 msg.sendToTarget();
                             }
 
-                            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                                    SystemClock.elapsedRealtime() +
-                                    mDhcpInfo.leaseDuration * 480,
-                                    mDhcpRenewalIntent);
+                            setDhcpRenewalAlarm(mDhcpInfo.leaseDuration);
                         } else {
                             event = EVENT_INTERFACE_CONFIGURATION_FAILED;
                             Log.d(TAG, "DHCP renewal failed: " + NetworkUtils.getDhcpError());
@@ -2602,6 +2596,19 @@
             return state == BluetoothHeadset.STATE_DISCONNECTED;
         }
 
+        private void setDhcpRenewalAlarm(long leaseDuration) {
+            //Do it a bit earlier than half the lease duration time
+            //to beat the native DHCP client and avoid extra packets
+            //48% for one hour lease time = 29 minutes
+            if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
+                leaseDuration = MIN_RENEWAL_TIME_SECS;
+            }
+            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime() +
+                    leaseDuration * 480, //in milliseconds
+                    mDhcpRenewalIntent);
+        }
+
     }
     
     private void checkUseStaticIp() {