Merge "Add auto-restore timeout for secondary networks." into honeycomb-LTE
diff --git a/core/java/android/net/DhcpInfoInternal.java b/core/java/android/net/DhcpInfoInternal.java
index 7396669..860da0a 100644
--- a/core/java/android/net/DhcpInfoInternal.java
+++ b/core/java/android/net/DhcpInfoInternal.java
@@ -22,6 +22,8 @@
 import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
 
 /**
  * A simple object for retrieving the results of a DHCP request.
@@ -31,7 +33,6 @@
 public class DhcpInfoInternal {
     private final static String TAG = "DhcpInfoInternal";
     public String ipAddress;
-    public String gateway;
     public int prefixLength;
 
     public String dns1;
@@ -40,7 +41,14 @@
     public String serverAddress;
     public int leaseDuration;
 
+    private Collection<RouteInfo> routes;
+
     public DhcpInfoInternal() {
+        routes = new ArrayList<RouteInfo>();
+    }
+
+    public void addRoute(RouteInfo routeInfo) {
+        routes.add(routeInfo);
     }
 
     private int convertToInt(String addr) {
@@ -58,7 +66,12 @@
     public DhcpInfo makeDhcpInfo() {
         DhcpInfo info = new DhcpInfo();
         info.ipAddress = convertToInt(ipAddress);
-        info.gateway = convertToInt(gateway);
+        for (RouteInfo route : routes) {
+            if (route.isDefaultRoute()) {
+                info.gateway = convertToInt(route.getGateway().getHostAddress());
+                break;
+            }
+        }
         try {
             InetAddress inetAddress = NetworkUtils.numericToInetAddress(ipAddress);
             info.netmask = NetworkUtils.prefixLengthToNetmaskInt(prefixLength);
@@ -81,8 +94,8 @@
     public LinkProperties makeLinkProperties() {
         LinkProperties p = new LinkProperties();
         p.addLinkAddress(makeLinkAddress());
-        if (TextUtils.isEmpty(gateway) == false) {
-            p.addGateway(NetworkUtils.numericToInetAddress(gateway));
+        for (RouteInfo route : routes) {
+            p.addRoute(route);
         }
         if (TextUtils.isEmpty(dns1) == false) {
             p.addDns(NetworkUtils.numericToInetAddress(dns1));
@@ -98,8 +111,10 @@
     }
 
     public String toString() {
+        String routeString = "";
+        for (RouteInfo route : routes) routeString += route.toString() + " | ";
         return "addr: " + ipAddress + "/" + prefixLength +
-                " gateway: " + gateway +
+                " routes: " + routeString +
                 " dns: " + dns1 + "," + dns2 +
                 " dhcpServer: " + serverAddress +
                 " leaseDuration: " + leaseDuration;
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
new file mode 100644
index 0000000..eaf087f
--- /dev/null
+++ b/core/java/android/net/DhcpStateMachine.java
@@ -0,0 +1,353 @@
+/*
+ * 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.net;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.DhcpInfoInternal;
+import android.net.NetworkUtils;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+/**
+ * StateMachine that interacts with the native DHCP client and can talk to
+ * a controller that also needs to be a StateMachine
+ *
+ * The Dhcp state machine provides the following features:
+ * - Wakeup and renewal using the native DHCP client  (which will not renew
+ *   on its own when the device is in suspend state and this can lead to device
+ *   holding IP address beyond expiry)
+ * - A notification right before DHCP request or renewal is started. This
+ *   can be used for any additional setup before DHCP. For example, wifi sets
+ *   BT-Wifi coex settings right before DHCP is initiated
+ *
+ * @hide
+ */
+public class DhcpStateMachine extends StateMachine {
+
+    private static final String TAG = "DhcpStateMachine";
+    private static final boolean DBG = false;
+
+
+    /* A StateMachine that controls the DhcpStateMachine */
+    private StateMachine mController;
+
+    private Context mContext;
+    private BroadcastReceiver mBroadcastReceiver;
+    private AlarmManager mAlarmManager;
+    private PendingIntent mDhcpRenewalIntent;
+    private PowerManager.WakeLock mDhcpRenewWakeLock;
+    private static final String WAKELOCK_TAG = "DHCP";
+
+    private static final int DHCP_RENEW = 0;
+    private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
+
+    private enum DhcpAction {
+        START,
+        RENEW
+    };
+
+    private String mInterfaceName;
+    private boolean mRegisteredForPreDhcpNotification = false;
+
+    private static final int BASE = Protocol.BASE_DHCP;
+
+    /* Commands from controller to start/stop DHCP */
+    public static final int CMD_START_DHCP                  = BASE + 1;
+    public static final int CMD_STOP_DHCP                   = BASE + 2;
+    public static final int CMD_RENEW_DHCP                  = BASE + 3;
+
+    /* Notification from DHCP state machine prior to DHCP discovery/renewal */
+    public static final int CMD_PRE_DHCP_ACTION             = BASE + 4;
+    /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
+     * success/failure */
+    public static final int CMD_POST_DHCP_ACTION            = BASE + 5;
+
+    /* Command from controller to indicate DHCP discovery/renewal can continue
+     * after pre DHCP action is complete */
+    public static final int CMD_PRE_DHCP_ACTION_COMPLETE    = BASE + 6;
+
+    /* Message.arg1 arguments to CMD_POST_DHCP notification */
+    public static final int DHCP_SUCCESS = 1;
+    public static final int DHCP_FAILURE = 2;
+
+    private State mDefaultState = new DefaultState();
+    private State mStoppedState = new StoppedState();
+    private State mWaitBeforeStartState = new WaitBeforeStartState();
+    private State mRunningState = new RunningState();
+    private State mWaitBeforeRenewalState = new WaitBeforeRenewalState();
+
+    private DhcpStateMachine(Context context, StateMachine controller, String intf) {
+        super(TAG);
+
+        mContext = context;
+        mController = controller;
+        mInterfaceName = intf;
+
+        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        Intent dhcpRenewalIntent = new Intent(ACTION_DHCP_RENEW, null);
+        mDhcpRenewalIntent = PendingIntent.getBroadcast(mContext, DHCP_RENEW, dhcpRenewalIntent, 0);
+
+        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                //DHCP renew
+                if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this);
+                //acquire a 40s wakelock to finish DHCP renewal
+                mDhcpRenewWakeLock.acquire(40000);
+                sendMessage(CMD_RENEW_DHCP);
+            }
+        };
+        mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
+
+        addState(mDefaultState);
+            addState(mStoppedState, mDefaultState);
+            addState(mWaitBeforeStartState, mDefaultState);
+            addState(mRunningState, mDefaultState);
+            addState(mWaitBeforeRenewalState, mDefaultState);
+
+        setInitialState(mStoppedState);
+    }
+
+    public static DhcpStateMachine makeDhcpStateMachine(Context context, StateMachine controller,
+            String intf) {
+        DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
+        dsm.start();
+        return dsm;
+    }
+
+    /**
+     * This sends a notification right before DHCP request/renewal so that the
+     * controller can do certain actions before DHCP packets are sent out.
+     * When the controller is ready, it sends a CMD_PRE_DHCP_ACTION_COMPLETE message
+     * to indicate DHCP can continue
+     *
+     * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
+     * handling during Dhcp
+     */
+    public void registerForPreDhcpNotification() {
+        mRegisteredForPreDhcpNotification = true;
+    }
+
+    class DefaultState extends State {
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_RENEW_DHCP:
+                    Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
+                    break;
+                case SM_QUIT_CMD:
+                    mContext.unregisterReceiver(mBroadcastReceiver);
+                    //let parent kill the state machine
+                    return NOT_HANDLED;
+                default:
+                    Log.e(TAG, "Error! unhandled message  " + message);
+                    break;
+            }
+            return HANDLED;
+        }
+    }
+
+
+    class StoppedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            boolean retValue = HANDLED;
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_START_DHCP:
+                    if (mRegisteredForPreDhcpNotification) {
+                        /* Notify controller before starting DHCP */
+                        mController.sendMessage(CMD_PRE_DHCP_ACTION);
+                        transitionTo(mWaitBeforeStartState);
+                    } else {
+                        if (runDhcp(DhcpAction.START)) {
+                            transitionTo(mRunningState);
+                        }
+                    }
+                    break;
+                case CMD_STOP_DHCP:
+                    //ignore
+                    break;
+                default:
+                    retValue = NOT_HANDLED;
+                    break;
+            }
+            return retValue;
+        }
+    }
+
+    class WaitBeforeStartState extends State {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            boolean retValue = HANDLED;
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_PRE_DHCP_ACTION_COMPLETE:
+                    if (runDhcp(DhcpAction.START)) {
+                        transitionTo(mRunningState);
+                    } else {
+                        transitionTo(mStoppedState);
+                    }
+                    break;
+                case CMD_STOP_DHCP:
+                    transitionTo(mStoppedState);
+                    break;
+                case CMD_START_DHCP:
+                    //ignore
+                    break;
+                default:
+                    retValue = NOT_HANDLED;
+                    break;
+            }
+            return retValue;
+        }
+    }
+
+    class RunningState extends State {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            boolean retValue = HANDLED;
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_STOP_DHCP:
+                    mAlarmManager.cancel(mDhcpRenewalIntent);
+                    if (!NetworkUtils.stopDhcp(mInterfaceName)) {
+                        Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
+                    }
+                    transitionTo(mStoppedState);
+                    break;
+                case CMD_RENEW_DHCP:
+                    if (mRegisteredForPreDhcpNotification) {
+                        /* Notify controller before starting DHCP */
+                        mController.sendMessage(CMD_PRE_DHCP_ACTION);
+                        transitionTo(mWaitBeforeRenewalState);
+                    } else {
+                        if (!runDhcp(DhcpAction.RENEW)) {
+                            transitionTo(mStoppedState);
+                        }
+                    }
+                    break;
+                case CMD_START_DHCP:
+                    //ignore
+                    break;
+                default:
+                    retValue = NOT_HANDLED;
+            }
+            return retValue;
+        }
+    }
+
+    class WaitBeforeRenewalState extends State {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            boolean retValue = HANDLED;
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_STOP_DHCP:
+                    mAlarmManager.cancel(mDhcpRenewalIntent);
+                    if (!NetworkUtils.stopDhcp(mInterfaceName)) {
+                        Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
+                    }
+                    transitionTo(mStoppedState);
+                    break;
+                case CMD_PRE_DHCP_ACTION_COMPLETE:
+                    if (runDhcp(DhcpAction.RENEW)) {
+                       transitionTo(mRunningState);
+                    } else {
+                       transitionTo(mStoppedState);
+                    }
+                    break;
+                case CMD_START_DHCP:
+                    //ignore
+                    break;
+                default:
+                    retValue = NOT_HANDLED;
+                    break;
+            }
+            return retValue;
+        }
+    }
+
+    private boolean runDhcp(DhcpAction dhcpAction) {
+        boolean success = false;
+        DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
+
+        if (dhcpAction == DhcpAction.START) {
+            Log.d(TAG, "DHCP request on " + mInterfaceName);
+            success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal);
+        } else if (dhcpAction == DhcpAction.RENEW) {
+            Log.d(TAG, "DHCP renewal on " + mInterfaceName);
+            success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal);
+        }
+
+        if (success) {
+            Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
+            //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() +
+                    dhcpInfoInternal.leaseDuration * 480, //in milliseconds
+                    mDhcpRenewalIntent);
+
+            mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
+                .sendToTarget();
+        } else {
+            Log.d(TAG, "DHCP failed on " + mInterfaceName + ": " +
+                    NetworkUtils.getDhcpError());
+            NetworkUtils.stopDhcp(mInterfaceName);
+            mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
+                .sendToTarget();
+        }
+        return success;
+    }
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index e88292f..61acf2b 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -54,7 +54,7 @@
     String mIfaceName;
     private Collection<LinkAddress> mLinkAddresses;
     private Collection<InetAddress> mDnses;
-    private Collection<InetAddress> mGateways;
+    private Collection<RouteInfo> mRoutes;
     private ProxyProperties mHttpProxy;
 
     public LinkProperties() {
@@ -67,7 +67,7 @@
             mIfaceName = source.getInterfaceName();
             mLinkAddresses = source.getLinkAddresses();
             mDnses = source.getDnses();
-            mGateways = source.getGateways();
+            mRoutes = source.getRoutes();
             mHttpProxy = new ProxyProperties(source.getHttpProxy());
         }
     }
@@ -104,11 +104,11 @@
         return Collections.unmodifiableCollection(mDnses);
     }
 
-    public void addGateway(InetAddress gateway) {
-        if (gateway != null) mGateways.add(gateway);
+    public void addRoute(RouteInfo route) {
+        if (route != null) mRoutes.add(route);
     }
-    public Collection<InetAddress> getGateways() {
-        return Collections.unmodifiableCollection(mGateways);
+    public Collection<RouteInfo> getRoutes() {
+        return Collections.unmodifiableCollection(mRoutes);
     }
 
     public void setHttpProxy(ProxyProperties proxy) {
@@ -122,7 +122,7 @@
         mIfaceName = null;
         mLinkAddresses = new ArrayList<LinkAddress>();
         mDnses = new ArrayList<InetAddress>();
-        mGateways = new ArrayList<InetAddress>();
+        mRoutes = new ArrayList<RouteInfo>();
         mHttpProxy = null;
     }
 
@@ -146,12 +146,12 @@
         for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
         dns += "] ";
 
-        String gateways = "Gateways: [";
-        for (InetAddress gw : mGateways) gateways += gw.getHostAddress() + ",";
-        gateways += "] ";
+        String routes = "Routes: [";
+        for (RouteInfo route : mRoutes) routes += route.toString() + ",";
+        routes += "] ";
         String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
 
-        return ifaceName + linkAddresses + gateways + dns + proxy;
+        return ifaceName + linkAddresses + routes + dns + proxy;
     }
 
 
@@ -177,7 +177,7 @@
 
         boolean sameAddresses;
         boolean sameDnses;
-        boolean sameGateways;
+        boolean sameRoutes;
 
         LinkProperties target = (LinkProperties) obj;
 
@@ -190,12 +190,12 @@
         sameDnses = (mDnses.size() == targetDnses.size()) ?
                 mDnses.containsAll(targetDnses) : false;
 
-        Collection<InetAddress> targetGateways = target.getGateways();
-        sameGateways = (mGateways.size() == targetGateways.size()) ?
-                mGateways.containsAll(targetGateways) : false;
+        Collection<RouteInfo> targetRoutes = target.getRoutes();
+        sameRoutes = (mRoutes.size() == targetRoutes.size()) ?
+                mRoutes.containsAll(targetRoutes) : false;
 
         return
-            sameAddresses && sameDnses && sameGateways
+            sameAddresses && sameDnses && sameRoutes
             && TextUtils.equals(getInterfaceName(), target.getInterfaceName())
             && (getHttpProxy() == null ? target.getHttpProxy() == null :
                 getHttpProxy().equals(target.getHttpProxy()));
@@ -211,7 +211,7 @@
         return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
                 + mLinkAddresses.size() * 31
                 + mDnses.size() * 37
-                + mGateways.size() * 41
+                + mRoutes.size() * 41
                 + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()));
     }
 
@@ -231,9 +231,9 @@
             dest.writeByteArray(d.getAddress());
         }
 
-        dest.writeInt(mGateways.size());
-        for(InetAddress gw : mGateways) {
-            dest.writeByteArray(gw.getAddress());
+        dest.writeInt(mRoutes.size());
+        for(RouteInfo route : mRoutes) {
+            dest.writeParcelable(route, flags);
         }
 
         if (mHttpProxy != null) {
@@ -272,9 +272,7 @@
                 }
                 addressCount = in.readInt();
                 for (int i=0; i<addressCount; i++) {
-                    try {
-                        netProp.addGateway(InetAddress.getByAddress(in.createByteArray()));
-                    } catch (UnknownHostException e) { }
+                    netProp.addRoute((RouteInfo)in.readParcelable(null));
                 }
                 if (in.readByte() == 1) {
                     netProp.setHttpProxy((ProxyProperties)in.readParcelable(null));
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index b3f3988..823d10f 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -80,6 +80,16 @@
     public native static boolean runDhcp(String interfaceName, DhcpInfoInternal ipInfo);
 
     /**
+     * Initiate renewal on the Dhcp client daemon. This call blocks until it obtains
+     * a result (either success or failure) from the daemon.
+     * @param interfaceName the name of the interface to configure
+     * @param ipInfo if the request succeeds, this object is filled in with
+     * the IP address information.
+     * @return {@code true} for success, {@code false} for failure
+     */
+    public native static boolean runDhcpRenew(String interfaceName, DhcpInfoInternal ipInfo);
+
+    /**
      * Shut down the DHCP client daemon.
      * @param interfaceName the name of the interface for which the daemon
      * should be stopped
diff --git a/core/java/android/net/RouteInfo.aidl b/core/java/android/net/RouteInfo.aidl
new file mode 100644
index 0000000..2296a57
--- /dev/null
+++ b/core/java/android/net/RouteInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.net;
+
+parcelable RouteInfo;
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
new file mode 100644
index 0000000..5b10531
--- /dev/null
+++ b/core/java/android/net/RouteInfo.java
@@ -0,0 +1,162 @@
+/*
+ * 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.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.UnknownHostException;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+/**
+ * A simple container for route information.
+ *
+ * @hide
+ */
+public class RouteInfo implements Parcelable {
+    /**
+     * The IP destination address for this route.
+     */
+    private final LinkAddress mDestination;
+
+    /**
+     * The gateway address for this route.
+     */
+    private final InetAddress mGateway;
+
+    private final boolean mIsDefault;
+
+    public RouteInfo(LinkAddress destination, InetAddress gateway) {
+        if (destination == null) {
+            try {
+                if ((gateway != null) && (gateway instanceof Inet4Address)) {
+                    destination = new LinkAddress(InetAddress.getByName("0.0.0.0"), 32);
+                } else {
+                    destination = new LinkAddress(InetAddress.getByName("::0"), 128);
+                }
+            } catch (Exception e) {}
+        }
+        mDestination = destination;
+        mGateway = gateway;
+        mIsDefault = isDefault();
+    }
+
+    public RouteInfo(InetAddress gateway) {
+        LinkAddress destination = null;
+        try {
+            if ((gateway != null) && (gateway instanceof Inet4Address)) {
+                destination = new LinkAddress(InetAddress.getByName("0.0.0.0"), 32);
+            } else {
+                destination = new LinkAddress(InetAddress.getByName("::0"), 128);
+            }
+        } catch (Exception e) {}
+        mDestination = destination;
+        mGateway = gateway;
+        mIsDefault = isDefault();
+    }
+
+    private boolean isDefault() {
+        boolean val = false;
+        if (mGateway != null) {
+            if (mGateway instanceof Inet4Address) {
+                val = (mDestination == null || mDestination.getNetworkPrefixLength() == 32);
+            } else {
+                val = (mDestination == null || mDestination.getNetworkPrefixLength() == 128);
+            }
+        }
+        return val;
+    }
+
+    public LinkAddress getDestination() {
+        return mDestination;
+    }
+
+    public InetAddress getGateway() {
+        return mGateway;
+    }
+
+    public boolean isDefaultRoute() {
+        return mIsDefault;
+    }
+
+    public String toString() {
+        String val = "";
+        if (mDestination != null) val = mDestination.toString();
+        if (mGateway != null) val += " -> " + mGateway.getHostAddress();
+        return val;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        if (mDestination == null) {
+            dest.writeByte((byte) 0);
+        } else {
+            dest.writeByte((byte) 1);
+            dest.writeByteArray(mDestination.getAddress().getAddress());
+            dest.writeInt(mDestination.getNetworkPrefixLength());
+        }
+
+        if (mGateway == null) {
+            dest.writeByte((byte) 0);
+        } else {
+            dest.writeByte((byte) 1);
+            dest.writeByteArray(mGateway.getAddress());
+        }
+    }
+
+    public static final Creator<RouteInfo> CREATOR =
+        new Creator<RouteInfo>() {
+        public RouteInfo createFromParcel(Parcel in) {
+            InetAddress destAddr = null;
+            int prefix = 0;
+            InetAddress gateway = null;
+
+            if (in.readByte() == 1) {
+                byte[] addr = in.createByteArray();
+                prefix = in.readInt();
+
+                try {
+                    destAddr = InetAddress.getByAddress(addr);
+                } catch (UnknownHostException e) {}
+            }
+
+            if (in.readByte() == 1) {
+                byte[] addr = in.createByteArray();
+
+                try {
+                    gateway = InetAddress.getByAddress(addr);
+                } catch (UnknownHostException e) {}
+            }
+
+            LinkAddress dest = null;
+
+            if (destAddr != null) {
+                dest = new LinkAddress(destAddr, prefix);
+            }
+
+            return new RouteInfo(dest, gateway);
+        }
+
+        public RouteInfo[] newArray(int size) {
+            return new RouteInfo[size];
+        }
+    };
+}
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 4c83515..27da3c3 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -637,6 +637,22 @@
                 }
                 return _result;
             }
+
+            public String[] getVolumeList() throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                String[] _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.readStringArray();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
         }
 
         private static final String DESCRIPTOR = "IMountService";
@@ -699,6 +715,8 @@
 
         static final int TRANSACTION_changeEncryptionPassword = IBinder.FIRST_CALL_TRANSACTION + 28;
 
+        static final int TRANSACTION_getVolumeList = IBinder.FIRST_CALL_TRANSACTION + 29;
+
         /**
          * Cast an IBinder object into an IMountService interface, generating a
          * proxy if needed.
@@ -1004,6 +1022,13 @@
                     reply.writeInt(result);
                     return true;
                 }
+                case TRANSACTION_getVolumeList: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String[] result = getVolumeList();
+                    reply.writeNoException();
+                    reply.writeStringArray(result);
+                    return true;
+                }
             }
             return super.onTransact(code, data, reply, flags);
         }
@@ -1179,4 +1204,8 @@
      */
     public int changeEncryptionPassword(String password) throws RemoteException;
 
+    /**
+     * Returns list of all mountable volumes.
+     */
+    public String[] getVolumeList() throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 73ac79f..234057b 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -527,4 +527,30 @@
 
         return null;
     }
+
+    /**
+     * Gets the state of a volume via its mountpoint.
+     * @hide
+     */
+    public String getVolumeState(String mountPoint) {
+        try {
+            return mMountService.getVolumeState(mountPoint);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get volume state", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns list of all mountable volumes.
+     * @hide
+     */
+    public String[] getVolumeList() {
+        try {
+            return mMountService.getVolumeList();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get volume list", e);
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index b59421e..c9b2f97 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -344,6 +344,13 @@
          */
         public interface FileColumns extends MediaColumns {
             /**
+             * The MTP storage ID of the file
+             * <P>Type: INTEGER</P>
+             * @hide
+             */
+            public static final String STORAGE_ID = "storage_id";
+
+            /**
              * The MTP format code of the file
              * <P>Type: INTEGER</P>
              * @hide
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index b35f615..2e7ec58 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -39,6 +39,7 @@
     public static final int BASE_WIFI                                               = 0x00020000;
     public static final int BASE_DHCP                                               = 0x00030000;
     public static final int BASE_DATA_CONNECTION                                    = 0x00040000;
+    public static final int BASE_DATA_CONNECTION_AC                                 = 0x00041000;
     public static final int BASE_DATA_CONNECTION_TRACKER                            = 0x00050000;
 
     //TODO: define all used protocols
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 3adf770..904eaf9 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -40,6 +40,16 @@
                     const char *dns2,
                     const char *server,
                     uint32_t  *lease);
+
+int dhcp_do_request_renew(const char *ifname,
+                    const char *ipaddr,
+                    const char *gateway,
+                    uint32_t  *prefixLength,
+                    const char *dns1,
+                    const char *dns2,
+                    const char *server,
+                    uint32_t  *lease);
+
 int dhcp_stop(const char *ifname);
 int dhcp_release_lease(const char *ifname);
 char *dhcp_get_errmsg();
@@ -58,7 +68,6 @@
     jclass dhcpInfoInternalClass;
     jmethodID constructorId;
     jfieldID ipaddress;
-    jfieldID gateway;
     jfieldID prefixLength;
     jfieldID dns1;
     jfieldID dns2;
@@ -146,7 +155,8 @@
     return (jint)result;
 }
 
-static jboolean android_net_utils_runDhcp(JNIEnv* env, jobject clazz, jstring ifname, jobject info)
+static jboolean android_net_utils_runDhcpCommon(JNIEnv* env, jobject clazz, jstring ifname,
+        jobject info, bool renew)
 {
     int result;
     char  ipaddr[PROPERTY_VALUE_MAX];
@@ -160,12 +170,41 @@
     const char *nameStr = env->GetStringUTFChars(ifname, NULL);
     if (nameStr == NULL) return (jboolean)false;
 
-    result = ::dhcp_do_request(nameStr, ipaddr, gateway, &prefixLength,
-                                        dns1, dns2, server, &lease);
+    if (renew) {
+        result = ::dhcp_do_request_renew(nameStr, ipaddr, gateway, &prefixLength,
+                dns1, dns2, server, &lease);
+    } else {
+        result = ::dhcp_do_request(nameStr, ipaddr, gateway, &prefixLength,
+                dns1, dns2, server, &lease);
+    }
+
     env->ReleaseStringUTFChars(ifname, nameStr);
     if (result == 0 && dhcpInfoInternalFieldIds.dhcpInfoInternalClass != NULL) {
         env->SetObjectField(info, dhcpInfoInternalFieldIds.ipaddress, env->NewStringUTF(ipaddr));
-        env->SetObjectField(info, dhcpInfoInternalFieldIds.gateway, env->NewStringUTF(gateway));
+
+        // set the gateway
+        jclass cls = env->FindClass("java/net/InetAddress");
+        jmethodID method = env->GetStaticMethodID(cls, "getByName",
+                "(Ljava/lang/String;)Ljava/net/InetAddress;");
+        jvalue args[1];
+        args[0].l = env->NewStringUTF(gateway);
+        jobject inetAddressObject = env->CallStaticObjectMethodA(cls, method, args);
+
+        if (!env->ExceptionOccurred()) {
+            cls = env->FindClass("android/net/RouteInfo");
+            method = env->GetMethodID(cls, "<init>", "(Ljava/net/InetAddress;)V");
+            args[0].l = inetAddressObject;
+            jobject routeInfoObject = env->NewObjectA(cls, method, args);
+
+            cls = env->FindClass("android/net/DhcpInfoInternal");
+            method = env->GetMethodID(cls, "addRoute", "(Landroid/net/RouteInfo;)V");
+            args[0].l = routeInfoObject;
+            env->CallVoidMethodA(info, method, args);
+        } else {
+            // if we have an exception (host not found perhaps), just don't add the route
+            env->ExceptionClear();
+        }
+
         env->SetIntField(info, dhcpInfoInternalFieldIds.prefixLength, prefixLength);
         env->SetObjectField(info, dhcpInfoInternalFieldIds.dns1, env->NewStringUTF(dns1));
         env->SetObjectField(info, dhcpInfoInternalFieldIds.dns2, env->NewStringUTF(dns2));
@@ -176,6 +215,17 @@
     return (jboolean)(result == 0);
 }
 
+static jboolean android_net_utils_runDhcp(JNIEnv* env, jobject clazz, jstring ifname, jobject info)
+{
+    return android_net_utils_runDhcpCommon(env, clazz, ifname, info, false);
+}
+
+static jboolean android_net_utils_runDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname, jobject info)
+{
+    return android_net_utils_runDhcpCommon(env, clazz, ifname, info, true);
+}
+
+
 static jboolean android_net_utils_stopDhcp(JNIEnv* env, jobject clazz, jstring ifname)
 {
     int result;
@@ -219,6 +269,7 @@
     { "removeDefaultRoute", "(Ljava/lang/String;)I",  (void *)android_net_utils_removeDefaultRoute },
     { "resetConnections", "(Ljava/lang/String;)I",  (void *)android_net_utils_resetConnections },
     { "runDhcp", "(Ljava/lang/String;Landroid/net/DhcpInfoInternal;)Z",  (void *)android_net_utils_runDhcp },
+    { "runDhcpRenew", "(Ljava/lang/String;Landroid/net/DhcpInfoInternal;)Z",  (void *)android_net_utils_runDhcpRenew },
     { "stopDhcp", "(Ljava/lang/String;)Z",  (void *)android_net_utils_stopDhcp },
     { "releaseDhcpLease", "(Ljava/lang/String;)Z",  (void *)android_net_utils_releaseDhcpLease },
     { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
@@ -233,7 +284,6 @@
     if (dhcpInfoInternalFieldIds.dhcpInfoInternalClass != NULL) {
         dhcpInfoInternalFieldIds.constructorId = env->GetMethodID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "<init>", "()V");
         dhcpInfoInternalFieldIds.ipaddress = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "ipAddress", "Ljava/lang/String;");
-        dhcpInfoInternalFieldIds.gateway = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "gateway", "Ljava/lang/String;");
         dhcpInfoInternalFieldIds.prefixLength = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "prefixLength", "I");
         dhcpInfoInternalFieldIds.dns1 = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "dns1", "Ljava/lang/String;");
         dhcpInfoInternalFieldIds.dns2 = env->GetFieldID(dhcpInfoInternalFieldIds.dhcpInfoInternalClass, "dns2", "Ljava/lang/String;");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0c9a2ef..bb31347 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -495,6 +495,12 @@
         android:label="@string/permlab_hardware_test"
         android:description="@string/permdesc_hardware_test" />
 
+    <!-- Allows access to configure network interfaces, configure/use IPSec, etc.
+         @hide -->
+    <permission android:name="android.permission.NET_ADMIN"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signature" />
+
     <!-- =========================================== -->
     <!-- Permissions associated with telephony state -->
     <!-- =========================================== -->
@@ -549,6 +555,14 @@
         android:description="@string/permdesc_sdcardWrite"
         android:protectionLevel="dangerous" />
 
+    <!-- Allows an application to write to internal media storage
+         @hide  -->
+    <permission android:name="android.permission.WRITE_MEDIA_STORAGE"
+        android:permissionGroup="android.permission-group.STORAGE"
+        android:label="@string/permlab_mediaStorageWrite"
+        android:description="@string/permdesc_mediaStorageWrite"
+        android:protectionLevel="signatureOrSystem" />
+
     <!-- ============================================ -->
     <!-- Permissions for low-level system interaction -->
     <!-- ============================================ -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dbeb815..f116025 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -114,6 +114,23 @@
          removable. -->
     <bool name="config_externalStorageRemovable" product="default">true</bool>
 
+    <!-- List of mount points for external storage devices.
+         The first item on the list should be the primary external storage and should match the
+         value returned by Environment.getExternalStorageDirectory (/mnt/sdcard).
+         MTP storage IDs will be generated based on the position of the mountpoint in this list:
+            0x00010001 - ID for primary external storage (/mnt/sdcard)
+            0x00020001 - ID for first secondary external storage
+            0x00030001 - ID for second secondary external storage
+         etc. -->
+    <string-array translatable="false" name="config_externalStoragePaths">
+        <item>"/mnt/sdcard"</item>
+    </string-array>
+
+    <!-- User visible descriptions of the volumes in the config_externalStoragePaths array. -->
+    <string-array translatable="true" name="config_externalStorageDescriptions">
+        <item>"SD card"</item>
+    </string-array>
+
     <!-- Number of megabytes of space to leave unallocated by MTP.
          MTP will subtract this value from the free space it reports back
          to the host via GetStorageInfo, and will not allow new files to
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
index d22356d..27363e8 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/AccessPointParserHelper.java
@@ -30,6 +30,7 @@
 import android.net.wifi.WifiConfiguration.ProxySettings;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.RouteInfo;
 import android.util.Log;
 
 import java.io.InputStream;
@@ -301,7 +302,7 @@
                     if (!InetAddress.isNumeric(gwAddr)) {
                         throw new SAXException();
                     }
-                    mLinkProperties.addGateway(InetAddress.getByName(gwAddr));
+                    mLinkProperties.addRoute(new RouteInfo(InetAddress.getByName(gwAddr)));
                 } catch (UnknownHostException e) {
                     throw new SAXException();
                 }
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index 50666b4..e3b6b5f 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.net.LinkProperties;
+import android.net.RouteInfo;
 import android.test.suitebuilder.annotation.SmallTest;
 import junit.framework.TestCase;
 
@@ -55,8 +56,8 @@
             source.addDns(NetworkUtils.numericToInetAddress(DNS1));
             source.addDns(NetworkUtils.numericToInetAddress(DNS2));
             // set 2 gateways
-            source.addGateway(NetworkUtils.numericToInetAddress(GATEWAY1));
-            source.addGateway(NetworkUtils.numericToInetAddress(GATEWAY2));
+            source.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1)));
+            source.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2)));
 
             LinkProperties target = new LinkProperties();
 
@@ -68,8 +69,8 @@
                     NetworkUtils.numericToInetAddress(ADDRV6), 128));
             target.addDns(NetworkUtils.numericToInetAddress(DNS1));
             target.addDns(NetworkUtils.numericToInetAddress(DNS2));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY1));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY2));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1)));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2)));
 
             assertTrue(source.equals(target));
             assertTrue(source.hashCode() == target.hashCode());
@@ -83,8 +84,8 @@
                     NetworkUtils.numericToInetAddress(ADDRV6), 128));
             target.addDns(NetworkUtils.numericToInetAddress(DNS1));
             target.addDns(NetworkUtils.numericToInetAddress(DNS2));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY1));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY2));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1)));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2)));
             assertFalse(source.equals(target));
 
             target.clear();
@@ -96,8 +97,8 @@
                     NetworkUtils.numericToInetAddress(ADDRV6), 128));
             target.addDns(NetworkUtils.numericToInetAddress(DNS1));
             target.addDns(NetworkUtils.numericToInetAddress(DNS2));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY1));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY2));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1)));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2)));
             assertFalse(source.equals(target));
 
             target.clear();
@@ -109,8 +110,8 @@
             // change dnses
             target.addDns(NetworkUtils.numericToInetAddress("75.208.7.2"));
             target.addDns(NetworkUtils.numericToInetAddress(DNS2));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY1));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY2));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1)));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2)));
             assertFalse(source.equals(target));
 
             target.clear();
@@ -122,8 +123,8 @@
             target.addDns(NetworkUtils.numericToInetAddress(DNS1));
             target.addDns(NetworkUtils.numericToInetAddress(DNS2));
             // change gateway
-            target.addGateway(NetworkUtils.numericToInetAddress("75.208.8.2"));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY2));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress("75.208.8.2")));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2)));
             assertFalse(source.equals(target));
 
         } catch (Exception e) {
@@ -146,8 +147,8 @@
             source.addDns(NetworkUtils.numericToInetAddress(DNS1));
             source.addDns(NetworkUtils.numericToInetAddress(DNS2));
             // set 2 gateways
-            source.addGateway(NetworkUtils.numericToInetAddress(GATEWAY1));
-            source.addGateway(NetworkUtils.numericToInetAddress(GATEWAY2));
+            source.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1)));
+            source.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2)));
 
             LinkProperties target = new LinkProperties();
             // Exchange order
@@ -158,8 +159,8 @@
                     NetworkUtils.numericToInetAddress(ADDRV4), 32));
             target.addDns(NetworkUtils.numericToInetAddress(DNS2));
             target.addDns(NetworkUtils.numericToInetAddress(DNS1));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY2));
-            target.addGateway(NetworkUtils.numericToInetAddress(GATEWAY1));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY2)));
+            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(GATEWAY1)));
 
             assertTrue(source.equals(target));
             assertTrue(source.hashCode() == target.hashCode());
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index df80546..5ed7966 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -58,10 +58,18 @@
         <group gid="sdcard_rw" />
     </permission>
 
+    <permission name="android.permission.WRITE_MEDIA_STORAGE" >
+        <group gid="media_rw" />
+    </permission>
+
     <permission name="android.permission.ACCESS_MTP" >
         <group gid="mtp" />
     </permission>
 
+    <permission name="android.permission.NET_ADMIN" >
+        <group gid="net_admin" />
+    </permission>
+
     <!-- The group that /cache belongs to, linked to the permission
          set on the applications that can access /cache -->
     <permission name="android.permission.ACCESS_CACHE_FILESYSTEM" >
diff --git a/docs/html/guide/topics/graphics/animation.jd b/docs/html/guide/topics/graphics/animation.jd
index 3b1716c..e10ab3e 100644
--- a/docs/html/guide/topics/graphics/animation.jd
+++ b/docs/html/guide/topics/graphics/animation.jd
@@ -903,7 +903,7 @@
     <code>"floatType"</code> unless you specify something else or if the <code>valuesFrom</code>
     and <code>valuesTo</code> values are colors.</dd>
 
-    <dt><code>android:startDelay</code></dt>
+    <dt><code>android:startOffset</code></dt>
 
     <dd>The delay, in milliseconds, before the animation begins playing (after calling {@link
     android.animation.ValueAnimator#start start()}).</dd>
diff --git a/include/media/mediascanner.h b/include/media/mediascanner.h
index df5be32..765c039 100644
--- a/include/media/mediascanner.h
+++ b/include/media/mediascanner.h
@@ -55,7 +55,7 @@
 
     status_t doProcessDirectory(
             char *path, int pathRemaining, MediaScannerClient &client,
-            ExceptionCheck exceptionCheck, void *exceptionEnv);
+            bool noMedia, ExceptionCheck exceptionCheck, void *exceptionEnv);
 
     MediaScanner(const MediaScanner &);
     MediaScanner &operator=(const MediaScanner &);
@@ -72,10 +72,9 @@
     void endFile();
 
     virtual bool scanFile(const char* path, long long lastModified,
-            long long fileSize, bool isDirectory) = 0;
+            long long fileSize, bool isDirectory, bool noMedia) = 0;
     virtual bool handleStringTag(const char* name, const char* value) = 0;
     virtual bool setMimeType(const char* mimeType) = 0;
-    virtual bool addNoMediaFolder(const char* path) = 0;
 
 protected:
     void convertValues(uint32_t encoding);
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 74d65d1..d1eb388 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -347,7 +347,6 @@
     private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
 
     private static class FileCacheEntry {
-        Uri mTableUri;
         long mRowId;
         String mPath;
         long mLastModified;
@@ -355,8 +354,7 @@
         boolean mSeenInFileSystem;
         boolean mLastModifiedChanged;
 
-        FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified, int format) {
-            mTableUri = tableUri;
+        FileCacheEntry(long rowId, String path, long lastModified, int format) {
             mRowId = rowId;
             mPath = path;
             mLastModified = lastModified;
@@ -367,7 +365,7 @@
 
         @Override
         public String toString() {
-            return mPath + " mTableUri: " + mTableUri + " mRowId: " + mRowId;
+            return mPath + " mRowId: " + mRowId;
         }
     }
 
@@ -425,41 +423,19 @@
         private long mFileSize;
         private String mWriter;
         private int mCompilation;
+        private boolean mNoMedia;   // flag to suppress file from appearing in media tables
 
         public FileCacheEntry beginFile(String path, String mimeType, long lastModified,
-                long fileSize, boolean isDirectory) {
+                long fileSize, boolean isDirectory, boolean noMedia) {
             mMimeType = mimeType;
             mFileType = 0;
             mFileSize = fileSize;
 
             if (!isDirectory) {
-                // special case certain file names
-                // I use regionMatches() instead of substring() below
-                // to avoid memory allocation
-                int lastSlash = path.lastIndexOf('/');
-                if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
-                    // ignore those ._* files created by MacOS
-                    if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
-                        return null;
-                    }
-
-                    // ignore album art files created by Windows Media Player:
-                    // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg
-                    // and AlbumArt_{...}_Small.jpg
-                    if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
-                        if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
-                                path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
-                            return null;
-                        }
-                        int length = path.length() - lastSlash - 1;
-                        if ((length == 17 && path.regionMatches(
-                                true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
-                                (length == 10
-                                 && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
-                            return null;
-                        }
-                    }
+                if (!noMedia && isNoMediaFile(path)) {
+                    noMedia = true;
                 }
+                mNoMedia = noMedia;
 
                 // try mimeType first, if it is specified
                 if (mimeType != null) {
@@ -491,23 +467,10 @@
             long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
             boolean wasModified = delta > 1 || delta < -1;
             if (entry == null || wasModified) {
-                Uri tableUri;
-                if (isDirectory) {
-                    tableUri = mFilesUri;
-                } else if (MediaFile.isVideoFileType(mFileType)) {
-                    tableUri = mVideoUri;
-                } else if (MediaFile.isImageFileType(mFileType)) {
-                    tableUri = mImagesUri;
-                } else if (MediaFile.isAudioFileType(mFileType)) {
-                    tableUri = mAudioUri;
-                } else {
-                    tableUri = mFilesUri;
-                }
                 if (wasModified) {
                     entry.mLastModified = lastModified;
-                    entry.mTableUri = tableUri;
                 } else {
-                    entry = new FileCacheEntry(tableUri, 0, path, lastModified,
+                    entry = new FileCacheEntry(0, path, lastModified,
                             (isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0));
                     mFileCache.put(key, entry);
                 }
@@ -539,36 +502,41 @@
             return entry;
         }
 
-        public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory) {
+        public void scanFile(String path, long lastModified, long fileSize,
+                boolean isDirectory, boolean noMedia) {
             // This is the callback funtion from native codes.
             // Log.v(TAG, "scanFile: "+path);
-            doScanFile(path, null, lastModified, fileSize, isDirectory, false);
+            doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
         }
 
         public Uri doScanFile(String path, String mimeType, long lastModified,
-                long fileSize, boolean isDirectory, boolean scanAlways) {
+                long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) {
             Uri result = null;
 //            long t1 = System.currentTimeMillis();
             try {
                 FileCacheEntry entry = beginFile(path, mimeType, lastModified,
-                        fileSize, isDirectory);
+                        fileSize, isDirectory, noMedia);
                 // rescan for metadata if file was modified since last scan
                 if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
-                    String lowpath = path.toLowerCase();
-                    boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);
-                    boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
-                    boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0);
-                    boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0);
-                    boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) ||
-                        (!ringtones && !notifications && !alarms && !podcasts);
+                    if (noMedia) {
+                        result = endFile(entry, false, false, false, false, false);
+                    } else {
+                        String lowpath = path.toLowerCase();
+                        boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);
+                        boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
+                        boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0);
+                        boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0);
+                        boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) ||
+                            (!ringtones && !notifications && !alarms && !podcasts);
 
-                    // we only extract metadata for audio and video files
-                    if (MediaFile.isAudioFileType(mFileType)
-                            || MediaFile.isVideoFileType(mFileType)) {
-                        processFile(path, mimeType, this);
+                        // we only extract metadata for audio and video files
+                        if (MediaFile.isAudioFileType(mFileType)
+                                || MediaFile.isVideoFileType(mFileType)) {
+                            processFile(path, mimeType, this);
+                        }
+
+                        result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
                     }
-
-                    result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
@@ -705,27 +673,31 @@
             map.put(MediaStore.MediaColumns.SIZE, mFileSize);
             map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
 
-            if (MediaFile.isVideoFileType(mFileType)) {
-                map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaStore.UNKNOWN_STRING));
-                map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaStore.UNKNOWN_STRING));
-                map.put(Video.Media.DURATION, mDuration);
-                // FIXME - add RESOLUTION
-            } else if (MediaFile.isImageFileType(mFileType)) {
-                // FIXME - add DESCRIPTION
-            } else if (MediaFile.isAudioFileType(mFileType)) {
-                map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
-                        mArtist : MediaStore.UNKNOWN_STRING);
-                map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
-                        mAlbumArtist.length() > 0) ? mAlbumArtist : null);
-                map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
-                        mAlbum : MediaStore.UNKNOWN_STRING);
-                map.put(Audio.Media.COMPOSER, mComposer);
-                if (mYear != 0) {
-                    map.put(Audio.Media.YEAR, mYear);
+            if (!mNoMedia) {
+                if (MediaFile.isVideoFileType(mFileType)) {
+                    map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0
+                            ? mArtist : MediaStore.UNKNOWN_STRING));
+                    map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0
+                            ? mAlbum : MediaStore.UNKNOWN_STRING));
+                    map.put(Video.Media.DURATION, mDuration);
+                    // FIXME - add RESOLUTION
+                } else if (MediaFile.isImageFileType(mFileType)) {
+                    // FIXME - add DESCRIPTION
+                } else if (MediaFile.isAudioFileType(mFileType)) {
+                    map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
+                            mArtist : MediaStore.UNKNOWN_STRING);
+                    map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
+                            mAlbumArtist.length() > 0) ? mAlbumArtist : null);
+                    map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
+                            mAlbum : MediaStore.UNKNOWN_STRING);
+                    map.put(Audio.Media.COMPOSER, mComposer);
+                    if (mYear != 0) {
+                        map.put(Audio.Media.YEAR, mYear);
+                    }
+                    map.put(Audio.Media.TRACK, mTrack);
+                    map.put(Audio.Media.DURATION, mDuration);
+                    map.put(Audio.Media.COMPILATION, mCompilation);
                 }
-                map.put(Audio.Media.TRACK, mTrack);
-                map.put(Audio.Media.DURATION, mDuration);
-                map.put(Audio.Media.COMPILATION, mCompilation);
             }
             return map;
         }
@@ -735,7 +707,7 @@
                 throws RemoteException {
             // update database
 
-             // use album artist if artist is missing
+            // use album artist if artist is missing
             if (mArtist == null || mArtist.length() == 0) {
                 mArtist = mAlbumArtist;
             }
@@ -777,7 +749,7 @@
                 values.put(Audio.Media.IS_ALARM, alarms);
                 values.put(Audio.Media.IS_MUSIC, music);
                 values.put(Audio.Media.IS_PODCAST, podcasts);
-            } else if (mFileType == MediaFile.FILE_TYPE_JPEG) {
+            } else if (mFileType == MediaFile.FILE_TYPE_JPEG && !mNoMedia) {
                 ExifInterface exif = null;
                 try {
                     exif = new ExifInterface(entry.mPath);
@@ -829,7 +801,16 @@
                 }
             }
 
-            Uri tableUri = entry.mTableUri;
+            Uri tableUri = mFilesUri;
+            if (!mNoMedia) {
+                if (MediaFile.isVideoFileType(mFileType)) {
+                    tableUri = mVideoUri;
+                } else if (MediaFile.isImageFileType(mFileType)) {
+                    tableUri = mImagesUri;
+                } else if (MediaFile.isAudioFileType(mFileType)) {
+                    tableUri = mAudioUri;
+                }
+            }
             Uri result = null;
             if (rowId == 0) {
                 if (mMtpObjectHandle != 0) {
@@ -939,25 +920,6 @@
             }
         }
 
-        public void addNoMediaFolder(String path) {
-            ContentValues values = new ContentValues();
-            values.put(MediaStore.Images.ImageColumns.DATA, "");
-            String [] pathSpec = new String[] {path + '%'};
-            try {
-                // These tables have DELETE_FILE triggers that delete the file from the
-                // sd card when deleting the database entry. We don't want to do this in
-                // this case, since it would cause those files to be removed if a .nomedia
-                // file was added after the fact, when in that case we only want the database
-                // entries to be removed.
-                mMediaProvider.update(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values,
-                        MediaStore.Images.ImageColumns.DATA + " LIKE ?", pathSpec);
-                mMediaProvider.update(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values,
-                        MediaStore.Images.ImageColumns.DATA + " LIKE ?", pathSpec);
-            } catch (RemoteException e) {
-                throw new RuntimeException();
-            }
-        }
-
         private int getFileTypeFromDrm(String path) {
             if (!isDrmEnabled()) {
                 return 0;
@@ -1021,13 +983,13 @@
                         // Only consider entries with absolute path names.
                         // This allows storing URIs in the database without the
                         // media scanner removing them.
-                        if (path.startsWith("/")) {
+                        if (path != null && path.startsWith("/")) {
                             String key = path;
                             if (mCaseInsensitivePaths) {
                                 key = path.toLowerCase();
                             }
 
-                            FileCacheEntry entry = new FileCacheEntry(mFilesUri, rowId, path,
+                            FileCacheEntry entry = new FileCacheEntry(rowId, path,
                                     lastModified, format);
                             mFileCache.put(key, entry);
                         }
@@ -1238,13 +1200,71 @@
 
             // always scan the file, so we can return the content://media Uri for existing files
             return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
-                    false, true);
+                    false, true, false);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
             return null;
         }
     }
 
+    private static boolean isNoMediaFile(String path) {
+        File file = new File(path);
+        if (file.isDirectory()) return false;
+
+        // special case certain file names
+        // I use regionMatches() instead of substring() below
+        // to avoid memory allocation
+        int lastSlash = path.lastIndexOf('/');
+        if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
+            // ignore those ._* files created by MacOS
+            if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
+                return true;
+            }
+
+            // ignore album art files created by Windows Media Player:
+            // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg
+            // and AlbumArt_{...}_Small.jpg
+            if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
+                if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
+                        path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
+                    return true;
+                }
+                int length = path.length() - lastSlash - 1;
+                if ((length == 17 && path.regionMatches(
+                        true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
+                        (length == 10
+                         && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public static boolean isNoMediaPath(String path) {
+        if (path == null) return false;
+
+        // return true if file or any parent directory has name starting with a dot
+        if (path.indexOf("/.") >= 0) return true;
+
+        // now check to see if any parent directories have a ".nomedia" file
+        // start from 1 so we don't bother checking in the root directory
+        int offset = 1;
+        while (offset >= 0) {
+            int slashIndex = path.indexOf('/', offset);
+            if (slashIndex > offset) {
+                slashIndex++; // move past slash
+                File file = new File(path.substring(0, slashIndex) + ".nomedia");
+                if (file.exists()) {
+                    // we have a .nomedia in one of the parent directories
+                    return true;
+                }
+            }
+            offset = slashIndex;
+        }
+        return isNoMediaFile(path);
+    }
+
     public void scanMtpFile(String path, String volumeName, int objectHandle, int format) {
         initialize(volumeName);
         MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
@@ -1289,7 +1309,7 @@
 
                 // always scan the file, so we can return the content://media Uri for existing files
                 mClient.doScanFile(path, mediaFileType.mimeType, lastModifiedSeconds, file.length(),
-                    (format == MtpConstants.FORMAT_ASSOCIATION), true);
+                    (format == MtpConstants.FORMAT_ASSOCIATION), true, isNoMediaPath(path));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
diff --git a/media/java/android/media/MediaScannerClient.java b/media/java/android/media/MediaScannerClient.java
index ac326ef..b326671 100644
--- a/media/java/android/media/MediaScannerClient.java
+++ b/media/java/android/media/MediaScannerClient.java
@@ -21,9 +21,8 @@
  */
 public interface MediaScannerClient
 {    
-    public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory);
-
-    public void addNoMediaFolder(String path);
+    public void scanFile(String path, long lastModified, long fileSize,
+            boolean isDirectory, boolean noMedia);
 
     /**
      * Called by native code to return metadata extracted from media files.
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index b4a4689..b900671 100644
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -50,7 +50,8 @@
     private final IContentProvider mMediaProvider;
     private final String mVolumeName;
     private final Uri mObjectsUri;
-    private final String mMediaStoragePath;
+    private final String mMediaStoragePath; // path to primary storage
+    private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
 
     // cached property groups for single properties
     private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
@@ -67,9 +68,6 @@
     private SharedPreferences mDeviceProperties;
     private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
 
-    // FIXME - this should be passed in via the constructor
-    private final int mStorageID = 0x00010001;
-
     private static final String[] ID_PROJECTION = new String[] {
             Files.FileColumns._ID, // 0
     };
@@ -85,17 +83,22 @@
     };
     private static final String[] OBJECT_INFO_PROJECTION = new String[] {
             Files.FileColumns._ID, // 0
-            Files.FileColumns.DATA, // 1
+            Files.FileColumns.STORAGE_ID, // 1
             Files.FileColumns.FORMAT, // 2
             Files.FileColumns.PARENT, // 3
-            Files.FileColumns.SIZE, // 4
-            Files.FileColumns.DATE_MODIFIED, // 5
+            Files.FileColumns.DATA, // 4
+            Files.FileColumns.SIZE, // 5
+            Files.FileColumns.DATE_MODIFIED, // 6
     };
     private static final String ID_WHERE = Files.FileColumns._ID + "=?";
     private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
     private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
     private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND "
                                             + Files.FileColumns.FORMAT + "=?";
+    private static final String PARENT_STORAGE_WHERE = PARENT_WHERE + " AND "
+                                            + Files.FileColumns.STORAGE_ID + "=?";
+    private static final String PARENT_STORAGE_FORMAT_WHERE = PARENT_STORAGE_WHERE + " AND "
+                                            + Files.FileColumns.FORMAT + "=?";
 
     private final MediaScanner mMediaScanner;
 
@@ -124,6 +127,14 @@
         }
     }
 
+    public void addStorage(MtpStorage storage) {
+        mStorageMap.put(storage.getPath(), storage);
+    }
+
+    public void removeStorage(MtpStorage storage) {
+        mStorageMap.remove(storage.getPath());
+    }
+
     private void initDeviceProperties(Context context) {
         final String devicePropertiesName = "device-properties";
         mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
@@ -160,7 +171,7 @@
     }
 
     private int beginSendObject(String path, int format, int parent,
-                         int storage, long size, long modified) {
+                         int storageId, long size, long modified) {
         // first make sure the object does not exist
         if (path != null) {
             Cursor c = null;
@@ -185,7 +196,7 @@
         values.put(Files.FileColumns.DATA, path);
         values.put(Files.FileColumns.FORMAT, format);
         values.put(Files.FileColumns.PARENT, parent);
-        // storage is ignored for now
+        values.put(Files.FileColumns.STORAGE_ID, storageId);
         values.put(Files.FileColumns.SIZE, size);
         values.put(Files.FileColumns.DATE_MODIFIED, modified);
 
@@ -237,19 +248,35 @@
         }
     }
 
-    private int[] getObjectList(int storageID, int format, int parent) {
-        // we can ignore storageID until we support multiple storages
-        Cursor c = null;
-        try {
+    private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
+        if (storageID != 0) {
             if (format != 0) {
-                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+                return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+                        PARENT_STORAGE_FORMAT_WHERE,
+                        new String[] { Integer.toString(parent), Integer.toString(storageID),
+                                Integer.toString(format) }, null);
+            } else {
+                return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+                        PARENT_STORAGE_WHERE, new String[]
+                                { Integer.toString(parent), Integer.toString(storageID) }, null);
+            }
+        } else {
+            if (format != 0) {
+                return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
                             PARENT_FORMAT_WHERE,
                             new String[] { Integer.toString(parent), Integer.toString(format) },
                              null);
             } else {
-                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+                return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
                             PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
             }
+        }
+    }
+
+    private int[] getObjectList(int storageID, int format, int parent) {
+        Cursor c = null;
+        try {
+            c = createObjectQuery(storageID, format, parent);
             if (c == null) {
                 return null;
             }
@@ -273,18 +300,9 @@
     }
 
     private int getNumObjects(int storageID, int format, int parent) {
-        // we can ignore storageID until we support multiple storages
         Cursor c = null;
         try {
-            if (format != 0) {
-                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
-                            PARENT_FORMAT_WHERE,
-                            new String[] { Integer.toString(parent), Integer.toString(format) },
-                             null);
-            } else {
-                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
-                            PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
-            }
+            c = createObjectQuery(storageID, format, parent);
             if (c != null) {
                 return c.getCount();
             }
@@ -508,7 +526,7 @@
             }
         }
 
-        return propertyGroup.getPropertyList((int)handle, format, depth, mStorageID);
+        return propertyGroup.getPropertyList((int)handle, format, depth);
     }
 
     private int renameFile(int handle, String newName) {
@@ -631,12 +649,12 @@
             c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
                             ID_WHERE, new String[] {  Integer.toString(handle) }, null);
             if (c != null && c.moveToNext()) {
-                outStorageFormatParent[0] = mStorageID;
+                outStorageFormatParent[0] = c.getInt(1);
                 outStorageFormatParent[1] = c.getInt(2);
                 outStorageFormatParent[2] = c.getInt(3);
 
                 // extract name from path
-                String path = c.getString(1);
+                String path = c.getString(4);
                 int lastSlash = path.lastIndexOf('/');
                 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
                 int end = path.length();
@@ -646,8 +664,8 @@
                 path.getChars(start, end, outName, 0);
                 outName[end - start] = 0;
 
-                outSizeModified[0] = c.getLong(4);
-                outSizeModified[1] = c.getLong(5);
+                outSizeModified[0] = c.getLong(5);
+                outSizeModified[1] = c.getLong(6);
                 return true;
             }
         } catch (RemoteException e) {
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index fceedd2..b75b11a 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -93,7 +93,7 @@
 
          switch (code) {
             case MtpConstants.PROPERTY_STORAGE_ID:
-                // no query needed until we support multiple storage units
+                column = Files.FileColumns.STORAGE_ID;
                 type = MtpConstants.TYPE_UINT32;
                 break;
              case MtpConstants.PROPERTY_OBJECT_FORMAT:
@@ -134,6 +134,7 @@
                 break;
             case MtpConstants.PROPERTY_PERSISTENT_UID:
                 // PUID is concatenation of storageID and object handle
+                column = Files.FileColumns.STORAGE_ID;
                 type = MtpConstants.TYPE_UINT128;
                 break;
             case MtpConstants.PROPERTY_DURATION:
@@ -280,7 +281,7 @@
         return path.substring(start, end);
     }
 
-    MtpPropertyList getPropertyList(int handle, int format, int depth, int storageID) {
+    MtpPropertyList getPropertyList(int handle, int format, int depth) {
         //Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth);
         if (depth > 1) {
             // we only support depth 0 and 1
@@ -348,10 +349,6 @@
 
                     // handle some special cases
                     switch (propertyCode) {
-                        case MtpConstants.PROPERTY_STORAGE_ID:
-                            result.append(handle, propertyCode, MtpConstants.TYPE_UINT32,
-                                    storageID);
-                            break;
                         case MtpConstants.PROPERTY_PROTECTION_STATUS:
                             // protection status is always 0
                             result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
@@ -398,7 +395,7 @@
                             break;
                         case MtpConstants.PROPERTY_PERSISTENT_UID:
                             // PUID is concatenation of storageID and object handle
-                            long puid = storageID;
+                            long puid = c.getLong(column);
                             puid <<= 32;
                             puid += handle;
                             result.append(handle, propertyCode, MtpConstants.TYPE_UINT128, puid);
diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java
index 006fa6d..c065ca8 100644
--- a/media/java/android/mtp/MtpServer.java
+++ b/media/java/android/mtp/MtpServer.java
@@ -33,8 +33,8 @@
         System.loadLibrary("media_jni");
     }
 
-    public MtpServer(MtpDatabase database, String storagePath, long reserveSpace) {
-        native_setup(database, storagePath, reserveSpace);
+    public MtpServer(MtpDatabase database) {
+        native_setup(database);
     }
 
     public void start() {
@@ -65,18 +65,20 @@
         native_set_ptp_mode(usePtp);
     }
 
-    // Used to disable MTP by removing all storage units.
-    // This is done to disable access to file transfer when the device is locked.
-    public void setLocked(boolean locked) {
-        native_set_locked(locked);
+    public void addStorage(MtpStorage storage) {
+        native_add_storage(storage);
     }
 
-    private native final void native_setup(MtpDatabase database, String storagePath,
-            long reserveSpace);
+    public void removeStorage(MtpStorage storage) {
+        native_remove_storage(storage.getStorageId());
+    }
+
+    private native final void native_setup(MtpDatabase database);
     private native final void native_start();
     private native final void native_stop();
     private native final void native_send_object_added(int handle);
     private native final void native_send_object_removed(int handle);
     private native final void native_set_ptp_mode(boolean usePtp);
-    private native final void native_set_locked(boolean locked);
+    private native final void native_add_storage(MtpStorage storage);
+    private native final void native_remove_storage(int storageId);
 }
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
new file mode 100644
index 0000000..33146e7
--- /dev/null
+++ b/media/java/android/mtp/MtpStorage.java
@@ -0,0 +1,89 @@
+/*
+ * 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.mtp;
+
+/**
+ * This class represents a storage unit on an MTP device.
+ * Used only for MTP support in USB responder mode.
+ * MtpStorageInfo is used in MTP host mode
+ *
+ * @hide
+ */
+public class MtpStorage {
+
+    private final int mStorageId;
+    private final String mPath;
+    private final String mDescription;
+    private final long mReserveSpace;
+
+    public MtpStorage(int id, String path, String description, long reserveSpace) {
+        mStorageId = id;
+        mPath = path;
+        mDescription = description;
+        mReserveSpace = reserveSpace;
+    }
+
+    /**
+     * Returns the storage ID for the storage unit
+     *
+     * @return the storage ID
+     */
+    public final int getStorageId() {
+        return mStorageId;
+    }
+
+    /**
+     * Generates a storage ID for storage of given index.
+     * Index 0 is for primary external storage
+     *
+     * @return the storage ID
+     */
+    public static int getStorageId(int index) {
+        // storage ID is 0x00010001 for primary storage,
+        // then 0x00020001, 0x00030001, etc. for secondary storages
+        return ((index + 1) << 16) + 1;
+    }
+
+   /**
+     * Returns the file path for the storage unit's storage in the file system
+     *
+     * @return the storage file path
+     */
+    public final String getPath() {
+        return mPath;
+    }
+
+   /**
+     * Returns the description string for the storage unit
+     *
+     * @return the storage unit description
+     */
+    public final String getDescription() {
+        return mDescription;
+    }
+
+   /**
+     * Returns the amount of space to reserve on the storage file system.
+     * This can be set to a non-zero value to prevent MTP from filling up the entire storage.
+     *
+     * @return the storage unit description
+     */
+    public final long getReserveSpace() {
+        return mReserveSpace;
+    }
+
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 2a89a2a..2ddb287 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -27,9 +27,11 @@
     libcamera_client \
     libsqlite \
     libmtp \
-    libusbhost
+    libusbhost \
+    libexif
 
 LOCAL_C_INCLUDES += \
+    external/jhead \
     external/tremor/Tremor \
     frameworks/base/core/jni \
     frameworks/base/media/libmedia \
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
index a5176fa..19f3ca3 100644
--- a/media/jni/android_media_MediaScanner.cpp
+++ b/media/jni/android_media_MediaScanner.cpp
@@ -62,13 +62,11 @@
         }
         else {
             mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",
-                                                     "(Ljava/lang/String;JJZ)V");
+                                                     "(Ljava/lang/String;JJZZ)V");
             mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",
                                                      "(Ljava/lang/String;Ljava/lang/String;)V");
             mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",
                                                      "(Ljava/lang/String;)V");
-            mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",
-                                                     "(Ljava/lang/String;)V");
         }
     }
     
@@ -79,13 +77,13 @@
     
     // returns true if it succeeded, false if an exception occured in the Java code
     virtual bool scanFile(const char* path, long long lastModified,
-            long long fileSize, bool isDirectory)
+            long long fileSize, bool isDirectory, bool noMedia)
     {
         jstring pathStr;
         if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
 
         mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
-                fileSize, isDirectory);
+                fileSize, isDirectory, noMedia);
 
         mEnv->DeleteLocalRef(pathStr);
         return (!mEnv->ExceptionCheck());
@@ -117,26 +115,12 @@
         return (!mEnv->ExceptionCheck());
     }
 
-    // returns true if it succeeded, false if an exception occured in the Java code
-    virtual bool addNoMediaFolder(const char* path)
-    {
-        jstring pathStr;
-        if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
-
-        mEnv->CallVoidMethod(mClient, mAddNoMediaFolderMethodID, pathStr);
-
-        mEnv->DeleteLocalRef(pathStr);
-        return (!mEnv->ExceptionCheck());
-    }
-
-
 private:
     JNIEnv *mEnv;
     jobject mClient;
     jmethodID mScanFileMethodID; 
     jmethodID mHandleStringTagMethodID; 
     jmethodID mSetMimeTypeMethodID;
-    jmethodID mAddNoMediaFolderMethodID;
 };
 
 
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 17d39e3..b78af44 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -29,11 +29,16 @@
 
 #include "MtpDatabase.h"
 #include "MtpDataPacket.h"
+#include "MtpObjectInfo.h"
 #include "MtpProperty.h"
 #include "MtpStringBuffer.h"
 #include "MtpUtils.h"
 #include "mtp.h"
 
+extern "C" {
+#include "jhead.h"
+}
+
 using namespace android;
 
 // ----------------------------------------------------------------------------
@@ -138,7 +143,9 @@
                                             MtpDataPacket& packet);
 
     virtual MtpResponseCode         getObjectInfo(MtpObjectHandle handle,
-                                            MtpDataPacket& packet);
+                                            MtpObjectInfo& info);
+
+    virtual void*                   getThumbnail(MtpObjectHandle handle, size_t& outThumbSize);
 
     virtual MtpResponseCode         getObjectFilePath(MtpObjectHandle handle,
                                             MtpString& outFilePath,
@@ -746,7 +753,7 @@
 }
 
 MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
-                                            MtpDataPacket& packet) {
+                                            MtpObjectInfo& info) {
     char    date[20];
 
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -756,50 +763,88 @@
         return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
 
     jint* intValues = env->GetIntArrayElements(mIntBuffer, 0);
-    MtpStorageID storageID = intValues[0];
-    MtpObjectFormat format = intValues[1];
-    MtpObjectHandle parent = intValues[2];
+    info.mStorageID = intValues[0];
+    info.mFormat = intValues[1];
+    info.mParent = intValues[2];
     env->ReleaseIntArrayElements(mIntBuffer, intValues, 0);
 
     jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
     uint64_t size = longValues[0];
-    uint64_t modified = longValues[1];
+    info.mCompressedSize = (size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size);
+    info.mDateModified = longValues[1];
     env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
 
-//    int associationType = (format == MTP_FORMAT_ASSOCIATION ?
+//    info.mAssociationType = (format == MTP_FORMAT_ASSOCIATION ?
 //                            MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
 //                            MTP_ASSOCIATION_TYPE_UNDEFINED);
-    int associationType = MTP_ASSOCIATION_TYPE_UNDEFINED;
-
-    packet.putUInt32(storageID);
-    packet.putUInt16(format);
-    packet.putUInt16(0);   // protection status
-    packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size));
-    packet.putUInt16(0);   // thumb format
-    packet.putUInt32(0);   // thumb compressed size
-    packet.putUInt32(0);   // thumb pix width
-    packet.putUInt32(0);   // thumb pix height
-    packet.putUInt32(0);   // image pix width
-    packet.putUInt32(0);   // image pix height
-    packet.putUInt32(0);   // image bit depth
-    packet.putUInt32(parent);
-    packet.putUInt16(associationType);
-    packet.putUInt32(0);   // association desc
-    packet.putUInt32(0);   // sequence number
+    info.mAssociationType = MTP_ASSOCIATION_TYPE_UNDEFINED;
 
     jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
-    packet.putString(str);   // file name
+    MtpString temp(str);
+    info.mName = strdup((const char *)temp);
     env->ReleaseCharArrayElements(mStringBuffer, str, 0);
 
-    packet.putEmptyString();
-    formatDateTime(modified, date, sizeof(date));
-    packet.putString(date);   // date modified
-    packet.putEmptyString();   // keywords
+    // read EXIF data for thumbnail information
+    if (info.mFormat == MTP_FORMAT_EXIF_JPEG || info.mFormat == MTP_FORMAT_JFIF) {
+        MtpString path;
+        int64_t length;
+        MtpObjectFormat format;
+        if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK) {
+            ResetJpgfile();
+             // Start with an empty image information structure.
+            memset(&ImageInfo, 0, sizeof(ImageInfo));
+            ImageInfo.FlashUsed = -1;
+            ImageInfo.MeteringMode = -1;
+            ImageInfo.Whitebalance = -1;
+            strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX);
+            if (ReadJpegFile((const char*)path, READ_METADATA)) {
+                Section_t* section = FindSection(M_EXIF);
+                if (section) {
+                    info.mThumbCompressedSize = ImageInfo.ThumbnailSize;
+                    info.mThumbFormat = MTP_FORMAT_EXIF_JPEG;
+                    info.mImagePixWidth = ImageInfo.Width;
+                    info.mImagePixHeight = ImageInfo.Height;
+                }
+            }
+            DiscardData();
+        }
+    }
 
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return MTP_RESPONSE_OK;
 }
 
+void* MyMtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) {
+    MtpString path;
+    int64_t length;
+    MtpObjectFormat format;
+    void* result = NULL;
+    outThumbSize = 0;
+
+    if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK
+            && (format == MTP_FORMAT_EXIF_JPEG || format == MTP_FORMAT_JFIF)) {
+        ResetJpgfile();
+         // Start with an empty image information structure.
+        memset(&ImageInfo, 0, sizeof(ImageInfo));
+        ImageInfo.FlashUsed = -1;
+        ImageInfo.MeteringMode = -1;
+        ImageInfo.Whitebalance = -1;
+        strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX);
+        if (ReadJpegFile((const char*)path, READ_METADATA)) {
+            Section_t* section = FindSection(M_EXIF);
+            if (section) {
+                outThumbSize = ImageInfo.ThumbnailSize;
+                result = malloc(outThumbSize);
+                if (result)
+                    memcpy(result, section->Data + ImageInfo.ThumbnailOffset + 8, outThumbSize);
+            }
+            DiscardData();
+        }
+    }
+
+    return result;
+}
+
 MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
                                             MtpString& outFilePath,
                                             int64_t& outFileLength,
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index e025ef1..c55189f 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -39,6 +39,17 @@
 
 using namespace android;
 
+// MtpStorage class
+jclass clazz_MtpStorage;
+
+// MtpStorage fields
+static jfieldID field_MtpStorage_storageId;
+static jfieldID field_MtpStorage_path;
+static jfieldID field_MtpStorage_description;
+static jfieldID field_MtpStorage_reserveSpace;
+
+static Mutex sMutex;
+
 // ----------------------------------------------------------------------------
 
 // in android_mtp_MtpDatabase.cpp
@@ -57,70 +68,77 @@
 private:
     MtpDatabase*    mDatabase;
     MtpServer*      mServer;
-    MtpStorage*     mStorage;
-    Mutex           mMutex;
+    MtpStorageList  mStorageList;
     bool            mUsePtp;
-    bool            mLocked;
     int             mFd;
 
 public:
-    MtpThread(MtpDatabase* database, MtpStorage* storage)
+    MtpThread(MtpDatabase* database)
         :   mDatabase(database),
             mServer(NULL),
-            mStorage(storage),
             mUsePtp(false),
-            mLocked(false),
             mFd(-1)
     {
     }
 
     virtual ~MtpThread() {
-        delete mStorage;
     }
 
     void setPtpMode(bool usePtp) {
-        mMutex.lock();
         mUsePtp = usePtp;
-        mMutex.unlock();
     }
 
-    void setLocked(bool locked) {
-        mMutex.lock();
-        if (locked != mLocked) {
-            if (mServer) {
-                if (locked)
-                    mServer->removeStorage(mStorage);
-                else
-                    mServer->addStorage(mStorage);
+    void addStorage(MtpStorage *storage) {
+        mStorageList.push(storage);
+        if (mServer)
+            mServer->addStorage(storage);
+    }
+
+    void removeStorage(MtpStorageID id) {
+        MtpStorage* storage = mServer->getStorage(id);
+        if (storage) {
+            for (int i = 0; i < mStorageList.size(); i++) {
+                if (mStorageList[i] == storage) {
+                    mStorageList.removeAt(i);
+                    break;
+                }
             }
-            mLocked = locked;
+            if (mServer)
+                mServer->removeStorage(storage);
+            delete storage;
         }
-        mMutex.unlock();
+    }
+
+    void start() {
+        run("MtpThread");
     }
 
     virtual bool threadLoop() {
-        mMutex.lock();
+        sMutex.lock();
+
         mFd = open("/dev/mtp_usb", O_RDWR);
         if (mFd >= 0) {
             ioctl(mFd, MTP_SET_INTERFACE_MODE,
                     (mUsePtp ? MTP_INTERFACE_MODE_PTP : MTP_INTERFACE_MODE_MTP));
 
             mServer = new MtpServer(mFd, mDatabase, AID_MEDIA_RW, 0664, 0775);
-            if (!mLocked)
-                mServer->addStorage(mStorage);
-
-            mMutex.unlock();
-            mServer->run();
-            mMutex.lock();
-
-            close(mFd);
-            mFd = -1;
-            delete mServer;
-            mServer = NULL;
+            for (int i = 0; i < mStorageList.size(); i++) {
+                mServer->addStorage(mStorageList[i]);
+            }
         } else {
             LOGE("could not open MTP driver, errno: %d", errno);
         }
-        mMutex.unlock();
+
+        sMutex.unlock();
+        mServer->run();
+        sMutex.lock();
+
+        close(mFd);
+        mFd = -1;
+        delete mServer;
+        mServer = NULL;
+
+        sMutex.unlock();
         // delay a bit before retrying to avoid excessive spin
         if (!exitPending()) {
             sleep(1);
@@ -130,17 +148,13 @@
     }
 
     void sendObjectAdded(MtpObjectHandle handle) {
-        mMutex.lock();
         if (mServer)
             mServer->sendObjectAdded(handle);
-        mMutex.unlock();
     }
 
     void sendObjectRemoved(MtpObjectHandle handle) {
-        mMutex.lock();
         if (mServer)
             mServer->sendObjectRemoved(handle);
-        mMutex.unlock();
     }
 };
 
@@ -150,18 +164,11 @@
 #endif // HAVE_ANDROID_OS
 
 static void
-android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase,
-        jstring storagePath, jlong reserveSpace)
+android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase)
 {
 #ifdef HAVE_ANDROID_OS
-    MtpDatabase* database = getMtpDatabase(env, javaDatabase);
-    const char *storagePathStr = env->GetStringUTFChars(storagePath, NULL);
-
     // create the thread and assign it to the smart pointer
-    MtpStorage* storage = new MtpStorage(MTP_FIRST_STORAGE_ID, storagePathStr, reserveSpace);
-    sThread = new MtpThread(database, storage);
-
-    env->ReleaseStringUTFChars(storagePath, storagePathStr);
+    sThread = new MtpThread(getMtpDatabase(env, javaDatabase));
 #endif
 }
 
@@ -169,9 +176,11 @@
 android_mtp_MtpServer_start(JNIEnv *env, jobject thiz)
 {
 #ifdef HAVE_ANDROID_OS
+   sMutex.lock();
     MtpThread *thread = sThread.get();
     if (thread)
-        thread->run("MtpThread");
+        thread->start();
+    sMutex.unlock();
 #endif // HAVE_ANDROID_OS
 }
 
@@ -179,11 +188,13 @@
 android_mtp_MtpServer_stop(JNIEnv *env, jobject thiz)
 {
 #ifdef HAVE_ANDROID_OS
+    sMutex.lock();
     MtpThread *thread = sThread.get();
     if (thread) {
         thread->requestExitAndWait();
         sThread = NULL;
     }
+    sMutex.unlock();
 #endif
 }
 
@@ -191,9 +202,11 @@
 android_mtp_MtpServer_send_object_added(JNIEnv *env, jobject thiz, jint handle)
 {
 #ifdef HAVE_ANDROID_OS
+    sMutex.lock();
     MtpThread *thread = sThread.get();
     if (thread)
         thread->sendObjectAdded(handle);
+    sMutex.unlock();
 #endif
 }
 
@@ -201,9 +214,11 @@
 android_mtp_MtpServer_send_object_removed(JNIEnv *env, jobject thiz, jint handle)
 {
 #ifdef HAVE_ANDROID_OS
+    sMutex.lock();
     MtpThread *thread = sThread.get();
     if (thread)
         thread->sendObjectRemoved(handle);
+    sMutex.unlock();
 #endif
 }
 
@@ -211,33 +226,68 @@
 android_mtp_MtpServer_set_ptp_mode(JNIEnv *env, jobject thiz, jboolean usePtp)
 {
 #ifdef HAVE_ANDROID_OS
+    sMutex.lock();
     MtpThread *thread = sThread.get();
     if (thread)
         thread->setPtpMode(usePtp);
+    sMutex.unlock();
 #endif
 }
 
 static void
-android_mtp_MtpServer_set_locked(JNIEnv *env, jobject thiz, jboolean locked)
+android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage)
 {
 #ifdef HAVE_ANDROID_OS
+    sMutex.lock();
+    MtpThread *thread = sThread.get();
+    if (thread) {
+        jint storageID = env->GetIntField(jstorage, field_MtpStorage_storageId);
+        jstring path = (jstring)env->GetObjectField(jstorage, field_MtpStorage_path);
+        jstring description = (jstring)env->GetObjectField(jstorage, field_MtpStorage_description);
+        jlong reserveSpace = env->GetLongField(jstorage, field_MtpStorage_reserveSpace);
+
+        const char *pathStr = env->GetStringUTFChars(path, NULL);
+        const char *descriptionStr = env->GetStringUTFChars(description, NULL);
+
+        MtpStorage* storage = new MtpStorage(storageID, pathStr, descriptionStr, reserveSpace);
+        thread->addStorage(storage);
+
+        env->ReleaseStringUTFChars(path, pathStr);
+        env->ReleaseStringUTFChars(description, descriptionStr);
+    } else {
+        LOGE("MtpThread is null in add_storage");
+    }
+    sMutex.unlock();
+#endif
+}
+
+static void
+android_mtp_MtpServer_remove_storage(JNIEnv *env, jobject thiz, jint storageId)
+{
+#ifdef HAVE_ANDROID_OS
+    sMutex.lock();
     MtpThread *thread = sThread.get();
     if (thread)
-        thread->setLocked(locked);
+        thread->removeStorage(storageId);
+    else
+        LOGE("MtpThread is null in remove_storage");
+    sMutex.unlock();
 #endif
 }
 
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
-    {"native_setup",                "(Landroid/mtp/MtpDatabase;Ljava/lang/String;J)V",
+    {"native_setup",                "(Landroid/mtp/MtpDatabase;)V",
                                             (void *)android_mtp_MtpServer_setup},
     {"native_start",                "()V",  (void *)android_mtp_MtpServer_start},
     {"native_stop",                 "()V",  (void *)android_mtp_MtpServer_stop},
     {"native_send_object_added",    "(I)V", (void *)android_mtp_MtpServer_send_object_added},
     {"native_send_object_removed",  "(I)V", (void *)android_mtp_MtpServer_send_object_removed},
     {"native_set_ptp_mode",         "(Z)V", (void *)android_mtp_MtpServer_set_ptp_mode},
-    {"native_set_locked",           "(Z)V", (void *)android_mtp_MtpServer_set_locked},
+    {"native_add_storage",          "(Landroid/mtp/MtpStorage;)V",
+                                            (void *)android_mtp_MtpServer_add_storage},
+    {"native_remove_storage",       "(I)V", (void *)android_mtp_MtpServer_remove_storage},
 };
 
 static const char* const kClassPathName = "android/mtp/MtpServer";
@@ -246,6 +296,33 @@
 {
     jclass clazz;
 
+    clazz = env->FindClass("android/mtp/MtpStorage");
+    if (clazz == NULL) {
+        LOGE("Can't find android/mtp/MtpStorage");
+        return -1;
+    }
+    field_MtpStorage_storageId = env->GetFieldID(clazz, "mStorageId", "I");
+    if (field_MtpStorage_storageId == NULL) {
+        LOGE("Can't find MtpStorage.mStorageId");
+        return -1;
+    }
+    field_MtpStorage_path = env->GetFieldID(clazz, "mPath", "Ljava/lang/String;");
+    if (field_MtpStorage_path == NULL) {
+        LOGE("Can't find MtpStorage.mPath");
+        return -1;
+    }
+    field_MtpStorage_description = env->GetFieldID(clazz, "mDescription", "Ljava/lang/String;");
+    if (field_MtpStorage_description == NULL) {
+        LOGE("Can't find MtpStorage.mDescription");
+        return -1;
+    }
+    field_MtpStorage_reserveSpace = env->GetFieldID(clazz, "mReserveSpace", "J");
+    if (field_MtpStorage_reserveSpace == NULL) {
+        LOGE("Can't find MtpStorage.mStorageId");
+        return -1;
+    }
+    clazz_MtpStorage = (jclass)env->NewGlobalRef(clazz);
+
     clazz = env->FindClass("android/mtp/MtpServer");
     if (clazz == NULL) {
         LOGE("Can't find android/mtp/MtpServer");
diff --git a/media/libmedia/MediaScanner.cpp b/media/libmedia/MediaScanner.cpp
index 5ec573e..28c86426 100644
--- a/media/libmedia/MediaScanner.cpp
+++ b/media/libmedia/MediaScanner.cpp
@@ -70,8 +70,7 @@
     client.setLocale(locale());
 
     status_t result =
-        doProcessDirectory(
-                pathBuffer, pathRemaining, client, exceptionCheck, exceptionEnv);
+        doProcessDirectory(pathBuffer, pathRemaining, client, false, exceptionCheck, exceptionEnv);
 
     free(pathBuffer);
 
@@ -80,20 +79,18 @@
 
 status_t MediaScanner::doProcessDirectory(
         char *path, int pathRemaining, MediaScannerClient &client,
-        ExceptionCheck exceptionCheck, void *exceptionEnv) {
+        bool noMedia, ExceptionCheck exceptionCheck, void *exceptionEnv) {
     // place to copy file or directory name
     char* fileSpot = path + strlen(path);
     struct dirent* entry;
     struct stat statbuf;
 
-    // ignore directories that contain a  ".nomedia" file
+    // Treat all files as non-media in directories that contain a  ".nomedia" file
     if (pathRemaining >= 8 /* strlen(".nomedia") */ ) {
         strcpy(fileSpot, ".nomedia");
         if (access(path, F_OK) == 0) {
-            LOGD("found .nomedia, skipping directory\n");
-            fileSpot[0] = 0;
-            client.addNoMediaFolder(path);
-            return OK;
+            LOGD("found .nomedia, setting noMedia flag\n");
+            noMedia = true;
         }
 
         // restore path
@@ -138,19 +135,21 @@
         }
         if (type == DT_REG || type == DT_DIR) {
             if (type == DT_DIR) {
-                // ignore directories with a name that starts with '.'
+                bool childNoMedia = noMedia;
+                // set noMedia flag on directories with a name that starts with '.'
                 // for example, the Mac ".Trashes" directory
-                if (name[0] == '.') continue;
+                if (name[0] == '.')
+                    childNoMedia = true;
 
                 // report the directory to the client
                 if (stat(path, &statbuf) == 0) {
-                    client.scanFile(path, statbuf.st_mtime, 0, true);
+                    client.scanFile(path, statbuf.st_mtime, 0, true, childNoMedia);
                 }
 
                 // and now process its contents
                 strcat(fileSpot, "/");
                 int err = doProcessDirectory(path, pathRemaining - nameLength - 1, client,
-                        exceptionCheck, exceptionEnv);
+                        childNoMedia, exceptionCheck, exceptionEnv);
                 if (err) {
                     // pass exceptions up - ignore other errors
                     if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
@@ -159,7 +158,7 @@
                 }
             } else {
                 stat(path, &statbuf);
-                client.scanFile(path, statbuf.st_mtime, statbuf.st_size, false);
+                client.scanFile(path, statbuf.st_mtime, statbuf.st_size, false, noMedia);
                 if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
             }
         }
diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp
index 0b0c80d..817eac05 100644
--- a/media/mtp/MtpDataPacket.cpp
+++ b/media/mtp/MtpDataPacket.cpp
@@ -388,6 +388,16 @@
     int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
     return (ret < 0 ? ret : 0);
 }
+
+int MtpDataPacket::writeData(int fd, void* data, uint32_t length) {
+    MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length + MTP_CONTAINER_HEADER_SIZE);
+    MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+    int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+    if (ret == MTP_CONTAINER_HEADER_SIZE)
+        ret = ::write(fd, data, length);
+    return (ret < 0 ? ret : 0);
+}
+
 #endif // MTP_DEVICE
 
 #ifdef MTP_HOST
diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h
index 577cea1..8a08948 100644
--- a/media/mtp/MtpDataPacket.h
+++ b/media/mtp/MtpDataPacket.h
@@ -100,6 +100,7 @@
     // write our data to the given file descriptor
     int                 write(int fd);
     int                 writeDataHeader(int fd, uint32_t length);
+    int                 writeData(int fd, void* data, uint32_t length);
 #endif
 
 #ifdef MTP_HOST
diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h
index 4d9a1ae..4e6ac7a 100644
--- a/media/mtp/MtpDatabase.h
+++ b/media/mtp/MtpDatabase.h
@@ -23,6 +23,7 @@
 
 class MtpDataPacket;
 class MtpProperty;
+class MtpObjectInfo;
 
 class MtpDatabase {
 public:
@@ -81,7 +82,9 @@
                                             MtpDataPacket& packet) = 0;
 
     virtual MtpResponseCode         getObjectInfo(MtpObjectHandle handle,
-                                            MtpDataPacket& packet) = 0;
+                                            MtpObjectInfo& info) = 0;
+
+    virtual void*                   getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) = 0;
 
     virtual MtpResponseCode         getObjectFilePath(MtpObjectHandle handle,
                                             MtpString& outFilePath,
diff --git a/media/mtp/MtpDebug.cpp b/media/mtp/MtpDebug.cpp
index 1668ecf..9f3037d 100644
--- a/media/mtp/MtpDebug.cpp
+++ b/media/mtp/MtpDebug.cpp
@@ -63,6 +63,12 @@
     { "MTP_OPERATION_GET_OBJECT_REFERENCES",        0x9810 },
     { "MTP_OPERATION_SET_OBJECT_REFERENCES",        0x9811 },
     { "MTP_OPERATION_SKIP",                         0x9820 },
+    // android extensions
+    { "MTP_OPERATION_GET_PARTIAL_OBJECT_64",        0x95C1 },
+    { "MTP_OPERATION_SEND_PARTIAL_OBJECT",          0x95C2 },
+    { "MTP_OPERATION_TRUNCATE_OBJECT",              0x95C3 },
+    { "MTP_OPERATION_BEGIN_EDIT_OBJECT",            0x95C4 },
+    { "MTP_OPERATION_END_EDIT_OBJECT",              0x95C5 },
     { 0,                                            0      },
 };
 
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 37e02a3..4a8fd3e 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -30,6 +30,7 @@
 
 #include "MtpDebug.h"
 #include "MtpDatabase.h"
+#include "MtpObjectInfo.h"
 #include "MtpProperty.h"
 #include "MtpServer.h"
 #include "MtpStorage.h"
@@ -49,7 +50,7 @@
     MTP_OPERATION_GET_OBJECT_HANDLES,
     MTP_OPERATION_GET_OBJECT_INFO,
     MTP_OPERATION_GET_OBJECT,
-//    MTP_OPERATION_GET_THUMB,
+    MTP_OPERATION_GET_THUMB,
     MTP_OPERATION_DELETE_OBJECT,
     MTP_OPERATION_SEND_OBJECT_INFO,
     MTP_OPERATION_SEND_OBJECT,
@@ -79,6 +80,12 @@
     MTP_OPERATION_GET_OBJECT_REFERENCES,
     MTP_OPERATION_SET_OBJECT_REFERENCES,
 //    MTP_OPERATION_SKIP,
+    // Android extension for direct file IO
+    MTP_OPERATION_GET_PARTIAL_OBJECT_64,
+    MTP_OPERATION_SEND_PARTIAL_OBJECT,
+    MTP_OPERATION_TRUNCATE_OBJECT,
+    MTP_OPERATION_BEGIN_EDIT_OBJECT,
+    MTP_OPERATION_END_EDIT_OBJECT,
 };
 
 static const MtpEventCode kSupportedEventCodes[] = {
@@ -218,6 +225,15 @@
         }
     }
 
+    // commit any open edits
+    int count = mObjectEditList.size();
+    for (int i = 0; i < count; i++) {
+        ObjectEdit* edit = mObjectEditList[i];
+        commitEdit(edit);
+        delete edit;
+    }
+    mObjectEditList.clear();
+
     if (mSessionOpen)
         mDatabase->sessionEnded();
 }
@@ -252,6 +268,39 @@
     }
 }
 
+void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path,
+        uint64_t size, MtpObjectFormat format, int fd) {
+    ObjectEdit*  edit = new ObjectEdit(handle, path, size, format, fd);
+    mObjectEditList.add(edit);
+}
+
+MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) {
+    int count = mObjectEditList.size();
+    for (int i = 0; i < count; i++) {
+        ObjectEdit* edit = mObjectEditList[i];
+        if (edit->mHandle == handle) return edit;
+    }
+    return NULL;
+}
+
+void MtpServer::removeEditObject(MtpObjectHandle handle) {
+    int count = mObjectEditList.size();
+    for (int i = 0; i < count; i++) {
+        ObjectEdit* edit = mObjectEditList[i];
+        if (edit->mHandle == handle) {
+            delete edit;
+            mObjectEditList.removeAt(i);
+            return;
+        }
+    }
+    LOGE("ObjectEdit not found in removeEditObject");
+}
+
+void MtpServer::commitEdit(ObjectEdit* edit) {
+    mDatabase->endSendObject((const char *)edit->mPath, edit->mHandle, edit->mFormat, true);
+}
+
+
 bool MtpServer::handleRequest() {
     Mutex::Autolock autoLock(mMutex);
 
@@ -321,8 +370,12 @@
         case MTP_OPERATION_GET_OBJECT:
             response = doGetObject();
             break;
+        case MTP_OPERATION_GET_THUMB:
+            response = doGetThumb();
+            break;
         case MTP_OPERATION_GET_PARTIAL_OBJECT:
-            response = doGetPartialObject();
+        case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
+            response = doGetPartialObject(operation);
             break;
         case MTP_OPERATION_SEND_OBJECT_INFO:
             response = doSendObjectInfo();
@@ -339,6 +392,18 @@
         case MTP_OPERATION_GET_DEVICE_PROP_DESC:
             response = doGetDevicePropDesc();
             break;
+        case MTP_OPERATION_SEND_PARTIAL_OBJECT:
+            response = doSendPartialObject();
+            break;
+        case MTP_OPERATION_TRUNCATE_OBJECT:
+            response = doTruncateObject();
+            break;
+        case MTP_OPERATION_BEGIN_EDIT_OBJECT:
+            response = doBeginEditObject();
+            break;
+        case MTP_OPERATION_END_EDIT_OBJECT:
+            response = doEndEditObject();
+            break;
         default:
             LOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
             response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
@@ -363,7 +428,7 @@
     mData.putUInt16(MTP_STANDARD_VERSION);
     mData.putUInt32(6); // MTP Vendor Extension ID
     mData.putUInt16(MTP_STANDARD_VERSION);
-    string.set("microsoft.com: 1.0;");
+    string.set("microsoft.com: 1.0; android.com: 1.0;");
     mData.putString(string); // MTP Extensions
     mData.putUInt16(0); //Functional Mode
     mData.putAUInt16(kSupportedOperationCodes,
@@ -601,7 +666,40 @@
     if (!hasStorage())
         return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
     MtpObjectHandle handle = mRequest.getParameter(1);
-    return mDatabase->getObjectInfo(handle, mData);
+    MtpObjectInfo info(handle);
+    MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
+    if (result == MTP_RESPONSE_OK) {
+        char    date[20];
+
+        mData.putUInt32(info.mStorageID);
+        mData.putUInt16(info.mFormat);
+        mData.putUInt16(info.mProtectionStatus);
+
+        // if object is being edited the database size may be out of date
+        uint32_t size = info.mCompressedSize;
+        ObjectEdit* edit = getEditObject(handle);
+        if (edit)
+            size = (edit->mSize > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->mSize);
+        mData.putUInt32(size);
+
+        mData.putUInt16(info.mThumbFormat);
+        mData.putUInt32(info.mThumbCompressedSize);
+        mData.putUInt32(info.mThumbPixWidth);
+        mData.putUInt32(info.mThumbPixHeight);
+        mData.putUInt32(info.mImagePixWidth);
+        mData.putUInt32(info.mImagePixHeight);
+        mData.putUInt32(info.mImagePixDepth);
+        mData.putUInt32(info.mParent);
+        mData.putUInt16(info.mAssociationType);
+        mData.putUInt32(info.mAssociationDesc);
+        mData.putUInt32(info.mSequenceNumber);
+        mData.putString(info.mName);
+        mData.putEmptyString();    // date created
+        formatDateTime(info.mDateModified, date, sizeof(date));
+        mData.putString(date);   // date modified
+        mData.putEmptyString();   // keywords
+    }
+    return result;
 }
 
 MtpResponseCode MtpServer::doGetObject() {
@@ -641,12 +739,38 @@
     return MTP_RESPONSE_OK;
 }
 
-MtpResponseCode MtpServer::doGetPartialObject() {
+MtpResponseCode MtpServer::doGetThumb() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    size_t thumbSize;
+    void* thumb = mDatabase->getThumbnail(handle, thumbSize);
+    if (thumb) {
+        // send data
+        mData.setOperationCode(mRequest.getOperationCode());
+        mData.setTransactionID(mRequest.getTransactionID());
+        mData.writeData(mFD, thumb, thumbSize);
+        free(thumb);
+        return MTP_RESPONSE_OK;
+    } else {
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+}
+
+MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) {
     if (!hasStorage())
         return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
     MtpObjectHandle handle = mRequest.getParameter(1);
-    uint32_t offset = mRequest.getParameter(2);
-    uint32_t length = mRequest.getParameter(3);
+    uint64_t offset;
+    uint32_t length;
+    offset = mRequest.getParameter(2);
+    if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) {
+        // android extension with 64 bit offset
+        uint64_t offset2 = mRequest.getParameter(3);
+        offset = offset | (offset2 << 32);
+        length = mRequest.getParameter(4);
+    } else {
+        // standard GetPartialObject
+        length = mRequest.getParameter(3);
+    }
     MtpString pathBuf;
     int64_t fileLength;
     MtpObjectFormat format;
@@ -933,4 +1057,113 @@
     return MTP_RESPONSE_OK;
 }
 
+MtpResponseCode MtpServer::doSendPartialObject() {
+    if (!hasStorage())
+        return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    uint64_t offset = mRequest.getParameter(2);
+    uint64_t offset2 = mRequest.getParameter(3);
+    offset = offset | (offset2 << 32);
+    uint32_t length = mRequest.getParameter(4);
+
+    ObjectEdit* edit = getEditObject(handle);
+    if (!edit) {
+        LOGE("object not open for edit in doSendPartialObject");
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+
+    // can't start writing past the end of the file
+    if (offset > edit->mSize) {
+        LOGD("writing past end of object, offset: %lld, edit->mSize: %lld", offset, edit->mSize);
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+
+    // read the header
+    int ret = mData.readDataHeader(mFD);
+    // FIXME - check for errors here.
+
+    // reset so we don't attempt to send this back
+    mData.reset();
+
+    const char* filePath = (const char *)edit->mPath;
+    LOGV("receiving partial %s %lld %ld\n", filePath, offset, length);
+    mtp_file_range  mfr;
+    mfr.fd = edit->mFD;
+    mfr.offset = offset;
+    mfr.length = length;
+
+    // transfer the file
+    ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
+    LOGV("MTP_RECEIVE_FILE returned %d", ret);
+    if (ret < 0) {
+        mResponse.setParameter(1, 0);
+        if (errno == ECANCELED)
+            return MTP_RESPONSE_TRANSACTION_CANCELLED;
+        else
+            return MTP_RESPONSE_GENERAL_ERROR;
+    }
+    mResponse.setParameter(1, length);
+    uint64_t end = offset + length;
+    if (end > edit->mSize) {
+        edit->mSize = end;
+    }
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doTruncateObject() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    ObjectEdit* edit = getEditObject(handle);
+    if (!edit) {
+        LOGE("object not open for edit in doTruncateObject");
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+
+    uint64_t offset = mRequest.getParameter(2);
+    uint64_t offset2 = mRequest.getParameter(3);
+    offset |= (offset2 << 32);
+    if (ftruncate(edit->mFD, offset) != 0) {
+        return MTP_RESPONSE_GENERAL_ERROR;
+    } else {
+        edit->mSize = offset;
+        return MTP_RESPONSE_OK;
+    }
+}
+
+MtpResponseCode MtpServer::doBeginEditObject() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    if (getEditObject(handle)) {
+        LOGE("object already open for edit in doBeginEditObject");
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+
+    MtpString path;
+    int64_t fileLength;
+    MtpObjectFormat format;
+    int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
+    if (result != MTP_RESPONSE_OK)
+        return result;
+
+    int fd = open((const char *)path, O_RDWR | O_EXCL);
+    if (fd < 0) {
+        LOGE("open failed for %s in doBeginEditObject (%d)", (const char *)path, errno);
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+
+    addEditObject(handle, path, fileLength, format, fd);
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doEndEditObject() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    ObjectEdit* edit = getEditObject(handle);
+    if (!edit) {
+        LOGE("object not open for edit in doEndEditObject");
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+
+    commitEdit(edit);
+    removeEditObject(handle);
+    return MTP_RESPONSE_OK;
+}
+
 }  // namespace android
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
index 1efa715..859a18e 100644
--- a/media/mtp/MtpServer.h
+++ b/media/mtp/MtpServer.h
@@ -65,11 +65,35 @@
 
     Mutex               mMutex;
 
+    // represents an MTP object that is being edited using the android extensions
+    // for direct editing (BeginEditObject, SendPartialObject, TruncateObject and EndEditObject)
+    class ObjectEdit {
+        public:
+        MtpObjectHandle     mHandle;
+        MtpString           mPath;
+        uint64_t            mSize;
+        MtpObjectFormat     mFormat;
+        int                 mFD;
+
+        ObjectEdit(MtpObjectHandle handle, const char* path, uint64_t size,
+            MtpObjectFormat format, int fd)
+                : mHandle(handle), mPath(path), mSize(size), mFormat(format), mFD(fd) {
+            }
+
+        virtual ~ObjectEdit() {
+            close(mFD);
+        }
+    };
+    Vector<ObjectEdit*>  mObjectEditList;
+
 public:
                         MtpServer(int fd, MtpDatabase* database,
                                     int fileGroup, int filePerm, int directoryPerm);
     virtual             ~MtpServer();
 
+    MtpStorage*         getStorage(MtpStorageID id);
+    inline bool         hasStorage() { return mStorages.size() > 0; }
+    bool                hasStorage(MtpStorageID id);
     void                addStorage(MtpStorage* storage);
     void                removeStorage(MtpStorage* storage);
 
@@ -79,13 +103,16 @@
     void                sendObjectRemoved(MtpObjectHandle handle);
 
 private:
-    MtpStorage*         getStorage(MtpStorageID id);
-    inline bool         hasStorage() { return mStorages.size() > 0; }
-    bool                hasStorage(MtpStorageID id);
     void                sendStoreAdded(MtpStorageID id);
     void                sendStoreRemoved(MtpStorageID id);
     void                sendEvent(MtpEventCode code, uint32_t param1);
 
+    void                addEditObject(MtpObjectHandle handle, MtpString& path,
+                                uint64_t size, MtpObjectFormat format, int fd);
+    ObjectEdit*         getEditObject(MtpObjectHandle handle);
+    void                removeEditObject(MtpObjectHandle handle);
+    void                commitEdit(ObjectEdit* edit);
+
     bool                handleRequest();
 
     MtpResponseCode     doGetDeviceInfo();
@@ -106,12 +133,17 @@
     MtpResponseCode     doGetObjectPropList();
     MtpResponseCode     doGetObjectInfo();
     MtpResponseCode     doGetObject();
-    MtpResponseCode     doGetPartialObject();
+    MtpResponseCode     doGetThumb();
+    MtpResponseCode     doGetPartialObject(MtpOperationCode operation);
     MtpResponseCode     doSendObjectInfo();
     MtpResponseCode     doSendObject();
     MtpResponseCode     doDeleteObject();
     MtpResponseCode     doGetObjectPropDesc();
     MtpResponseCode     doGetDevicePropDesc();
+    MtpResponseCode     doSendPartialObject();
+    MtpResponseCode     doTruncateObject();
+    MtpResponseCode     doBeginEditObject();
+    MtpResponseCode     doEndEditObject();
 };
 
 }; // namespace android
diff --git a/media/mtp/MtpStorage.cpp b/media/mtp/MtpStorage.cpp
index 6cb88b3..fff0b5f 100644
--- a/media/mtp/MtpStorage.cpp
+++ b/media/mtp/MtpStorage.cpp
@@ -32,9 +32,11 @@
 
 namespace android {
 
-MtpStorage::MtpStorage(MtpStorageID id, const char* filePath, uint64_t reserveSpace)
+MtpStorage::MtpStorage(MtpStorageID id, const char* filePath,
+        const char* description, uint64_t reserveSpace)
     :   mStorageID(id),
         mFilePath(filePath),
+        mDescription(description),
         mMaxCapacity(0),
         mReserveSpace(reserveSpace)
 {
@@ -75,7 +77,7 @@
 }
 
 const char* MtpStorage::getDescription() const {
-    return "Device Storage";
+    return (const char *)mDescription;
 }
 
 }  // namespace android
diff --git a/media/mtp/MtpStorage.h b/media/mtp/MtpStorage.h
index 858c9d3..d6ad25f 100644
--- a/media/mtp/MtpStorage.h
+++ b/media/mtp/MtpStorage.h
@@ -29,13 +29,14 @@
 private:
     MtpStorageID            mStorageID;
     MtpString               mFilePath;
+    MtpString               mDescription;
     uint64_t                mMaxCapacity;
     // amount of free space to leave unallocated
     uint64_t                mReserveSpace;
 
 public:
                             MtpStorage(MtpStorageID id, const char* filePath,
-                                    uint64_t reserveSpace);
+                                    const char* description, uint64_t reserveSpace);
     virtual                 ~MtpStorage();
 
     inline MtpStorageID     getStorageID() const { return mStorageID; }
diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h
index 6fedc16..d270df5 100644
--- a/media/mtp/mtp.h
+++ b/media/mtp/mtp.h
@@ -22,8 +22,6 @@
 
 #define MTP_STANDARD_VERSION            100
 
-#define MTP_FIRST_STORAGE_ID            0x00010001
-
 // Container Types
 #define MTP_CONTAINER_TYPE_UNDEFINED    0
 #define MTP_CONTAINER_TYPE_COMMAND      1
@@ -393,6 +391,19 @@
 #define MTP_OPERATION_SET_OBJECT_REFERENCES                 0x9811
 #define MTP_OPERATION_SKIP                                  0x9820
 
+// Android extensions for direct file IO
+
+// Same as GetPartialObject, but with 64 bit offset
+#define MTP_OPERATION_GET_PARTIAL_OBJECT_64                 0x95C1
+// Same as GetPartialObject64, but copying host to device
+#define MTP_OPERATION_SEND_PARTIAL_OBJECT                   0x95C2
+// Truncates file to 64 bit length
+#define MTP_OPERATION_TRUNCATE_OBJECT                       0x95C3
+// Must be called before using SendPartialObject and TruncateObject
+#define MTP_OPERATION_BEGIN_EDIT_OBJECT                     0x95C4
+// Called to commit changes made by SendPartialObject and TruncateObject
+#define MTP_OPERATION_END_EDIT_OBJECT                       0x95C5
+
 // MTP Response Codes
 #define MTP_RESPONSE_UNDEFINED                                  0x2000
 #define MTP_RESPONSE_OK                                         0x2001
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/HeightReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/HeightReceiver.java
index 90c9568..9924faa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/HeightReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/HeightReceiver.java
@@ -77,7 +77,9 @@
         if (plugged) {
             final DisplayMetrics metrics = new DisplayMetrics();
             mWindowManager.getDefaultDisplay().getMetrics(metrics);
-            height = metrics.heightPixels - 720;
+            //Slog.i(TAG, "setPlugged: display metrics=" + metrics);
+            final int shortSide = Math.min(metrics.widthPixels, metrics.heightPixels);
+            height = shortSide - 720;
         }
 
         final int minHeight
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 78fdf7f..428c94f 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -34,6 +34,7 @@
 import android.net.NetworkUtils;
 import android.net.Proxy;
 import android.net.ProxyProperties;
+import android.net.RouteInfo;
 import android.net.vpn.VpnManager;
 import android.net.wifi.WifiStateTracker;
 import android.os.Binder;
@@ -1417,14 +1418,19 @@
         if (p == null) return;
         String interfaceName = p.getInterfaceName();
         if (TextUtils.isEmpty(interfaceName)) return;
-        for (InetAddress gateway : p.getGateways()) {
+        for (RouteInfo route : p.getRoutes()) {
 
-            if (NetworkUtils.addHostRoute(interfaceName, gateway, null) &&
-                    NetworkUtils.addDefaultRoute(interfaceName, gateway)) {
-                if (DBG) {
-                    NetworkInfo networkInfo = nt.getNetworkInfo();
-                    log("addDefaultRoute for " + networkInfo.getTypeName() +
-                            " (" + interfaceName + "), GatewayAddr=" + gateway.getHostAddress());
+            //TODO - handle non-default routes
+            if (route.isDefaultRoute()) {
+                InetAddress gateway = route.getGateway();
+                if (NetworkUtils.addHostRoute(interfaceName, gateway, null) &&
+                        NetworkUtils.addDefaultRoute(interfaceName, gateway)) {
+                    if (DBG) {
+                        NetworkInfo networkInfo = nt.getNetworkInfo();
+                        log("addDefaultRoute for " + networkInfo.getTypeName() +
+                                " (" + interfaceName + "), GatewayAddr=" +
+                                gateway.getHostAddress());
+                    }
                 }
             }
         }
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 91ada6b..a100f1f 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -64,6 +64,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactory;
@@ -144,7 +145,8 @@
 
     private Context                               mContext;
     private NativeDaemonConnector                 mConnector;
-    private String                                mLegacyState = Environment.MEDIA_REMOVED;
+    private final HashMap<String, String>         mVolumeStates = new HashMap<String, String>();
+    private String                                mExternalStoragePath;
     private PackageManagerService                 mPms;
     private boolean                               mUmsEnabling;
     // Used as a lock for methods that register/unregister listeners.
@@ -442,29 +444,54 @@
                  * to make the media scanner run.
                  */
                 if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
-                    notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted);
+                    notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia,
+                            VolumeState.Mounted);
                     return;
                 }
                 new Thread() {
                     @Override
                     public void run() {
                         try {
-                            String path = Environment.getExternalStorageDirectory().getPath();
-                            String state = getVolumeState(path);
-
-                            if (mEmulateExternalStorage) {
-                                notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Mounted);
-                            } else if (state.equals(Environment.MEDIA_UNMOUNTED)) {
-                                int rc = doMountVolume(path);
-                                if (rc != StorageResultCode.OperationSucceeded) {
-                                    Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc));
+                            // it is not safe to call vold with mVolumeStates locked
+                            // so we make a copy of the paths and states and process them
+                            // outside the lock
+                            String[] paths, states;
+                            int count;
+                            synchronized (mVolumeStates) {
+                                Set<String> keys = mVolumeStates.keySet();
+                                count = keys.size();
+                                paths = (String[])keys.toArray(new String[count]);
+                                states = new String[count];
+                                for (int i = 0; i < count; i++) {
+                                    states[i] = mVolumeStates.get(paths[i]);
                                 }
-                            } else if (state.equals(Environment.MEDIA_SHARED)) {
-                                /*
-                                 * Bootstrap UMS enabled state since vold indicates
-                                 * the volume is shared (runtime restart while ums enabled)
-                                 */
-                                notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared);
+                            }
+
+                            for (int i = 0; i < count; i++) {
+                                String path = paths[i];
+                                String state = states[i];
+
+                                if (state.equals(Environment.MEDIA_UNMOUNTED)) {
+                                    int rc = doMountVolume(path);
+                                    if (rc != StorageResultCode.OperationSucceeded) {
+                                        Slog.e(TAG, String.format("Boot-time mount failed (%d)",
+                                                rc));
+                                    }
+                                } else if (state.equals(Environment.MEDIA_SHARED)) {
+                                    /*
+                                     * Bootstrap UMS enabled state since vold indicates
+                                     * the volume is shared (runtime restart while ums enabled)
+                                     */
+                                    notifyVolumeStateChange(null, path, VolumeState.NoMedia,
+                                            VolumeState.Shared);
+                                }
+                            }
+
+                            /* notify external storage has mounted to trigger media scanner */
+                            if (mEmulateExternalStorage) {
+                                notifyVolumeStateChange(null,
+                                        Environment.getExternalStorageDirectory().getPath(),
+                                        VolumeState.NoMedia, VolumeState.Mounted);
                             }
 
                             /*
@@ -516,35 +543,36 @@
     }
 
     private void updatePublicVolumeState(String path, String state) {
-        if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
-            Slog.w(TAG, "Multiple volumes not currently supported");
+        String oldState;
+        synchronized(mVolumeStates) {
+            oldState = mVolumeStates.put(path, state);
+        }
+        if (state.equals(oldState)) {
+            Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
+                    state, state, path));
             return;
         }
 
-        if (mLegacyState.equals(state)) {
-            Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
-            return;
-        }
-        // Update state on PackageManager, but only of real events
-        if (!mEmulateExternalStorage) {
-            if (Environment.MEDIA_UNMOUNTED.equals(state)) {
-                mPms.updateExternalMediaStatus(false, false);
+        Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
 
-                /*
-                 * Some OBBs might have been unmounted when this volume was
-                 * unmounted, so send a message to the handler to let it know to
-                 * remove those from the list of mounted OBBS.
-                 */
-                mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE,
-                        path));
-            } else if (Environment.MEDIA_MOUNTED.equals(state)) {
-                mPms.updateExternalMediaStatus(true, false);
+        if (path.equals(mExternalStoragePath)) {
+            // Update state on PackageManager, but only of real events
+            if (!mEmulateExternalStorage) {
+                if (Environment.MEDIA_UNMOUNTED.equals(state)) {
+                    mPms.updateExternalMediaStatus(false, false);
+
+                    /*
+                     * Some OBBs might have been unmounted when this volume was
+                     * unmounted, so send a message to the handler to let it know to
+                     * remove those from the list of mounted OBBS.
+                     */
+                    mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
+                            OBB_FLUSH_MOUNT_STATE, path));
+                } else if (Environment.MEDIA_MOUNTED.equals(state)) {
+                    mPms.updateExternalMediaStatus(true, false);
+                }
             }
         }
-
-        String oldState = mLegacyState;
-        mLegacyState = state;
-
         synchronized (mListeners) {
             for (int i = mListeners.size() -1; i >= 0; i--) {
                 MountServiceBinderListener bl = mListeners.get(i);
@@ -575,20 +603,15 @@
                 /**
                  * Determine media state and UMS detection status
                  */
-                String path = Environment.getExternalStorageDirectory().getPath();
-                String state = Environment.MEDIA_REMOVED;
-
                 try {
                     String[] vols = mConnector.doListCommand(
                         "volume list", VoldResponseCode.VolumeListResult);
                     for (String volstr : vols) {
                         String[] tok = volstr.split(" ");
                         // FMT: <label> <mountpoint> <state>
-                        if (!tok[1].equals(path)) {
-                            Slog.w(TAG, String.format(
-                                    "Skipping unknown volume '%s'",tok[1]));
-                            continue;
-                        }
+                        String path = tok[1];
+                        String state = Environment.MEDIA_REMOVED;
+
                         int st = Integer.parseInt(tok[2]);
                         if (st == VolumeState.NoMedia) {
                             state = Environment.MEDIA_REMOVED;
@@ -603,14 +626,15 @@
                         } else {
                             throw new Exception(String.format("Unexpected state %d", st));
                         }
-                    }
-                    if (state != null) {
-                        if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
-                        updatePublicVolumeState(path, state);
+
+                        if (state != null) {
+                            if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
+                            updatePublicVolumeState(path, state);
+                        }
                     }
                 } catch (Exception e) {
                     Slog.e(TAG, "Error processing initial volume state", e);
-                    updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
+                    updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED);
                 }
 
                 try {
@@ -1052,11 +1076,12 @@
     public MountService(Context context) {
         mContext = context;
 
+        mExternalStoragePath = Environment.getExternalStorageDirectory().getPath();
         mEmulateExternalStorage = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_emulateExternalStorage);
         if (mEmulateExternalStorage) {
             Slog.d(TAG, "using emulated external storage");
-            mLegacyState = Environment.MEDIA_MOUNTED;
+            mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED);
         }
 
         // XXX: This will go away soon in favor of IMountServiceObserver
@@ -1125,54 +1150,56 @@
         validatePermission(android.Manifest.permission.SHUTDOWN);
 
         Slog.i(TAG, "Shutting down");
+        synchronized (mVolumeStates) {
+            for (String path : mVolumeStates.keySet()) {
+                String state = mVolumeStates.get(path);
 
-        String path = Environment.getExternalStorageDirectory().getPath();
-        String state = getVolumeState(path);
-
-        if (state.equals(Environment.MEDIA_SHARED)) {
-            /*
-             * If the media is currently shared, unshare it.
-             * XXX: This is still dangerous!. We should not
-             * be rebooting at *all* if UMS is enabled, since
-             * the UMS host could have dirty FAT cache entries
-             * yet to flush.
-             */
-            setUsbMassStorageEnabled(false);
-        } else if (state.equals(Environment.MEDIA_CHECKING)) {
-            /*
-             * If the media is being checked, then we need to wait for
-             * it to complete before being able to proceed.
-             */
-            // XXX: @hackbod - Should we disable the ANR timer here?
-            int retries = 30;
-            while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException iex) {
-                    Slog.e(TAG, "Interrupted while waiting for media", iex);
-                    break;
+                if (state.equals(Environment.MEDIA_SHARED)) {
+                    /*
+                     * If the media is currently shared, unshare it.
+                     * XXX: This is still dangerous!. We should not
+                     * be rebooting at *all* if UMS is enabled, since
+                     * the UMS host could have dirty FAT cache entries
+                     * yet to flush.
+                     */
+                    setUsbMassStorageEnabled(false);
+                } else if (state.equals(Environment.MEDIA_CHECKING)) {
+                    /*
+                     * If the media is being checked, then we need to wait for
+                     * it to complete before being able to proceed.
+                     */
+                    // XXX: @hackbod - Should we disable the ANR timer here?
+                    int retries = 30;
+                    while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
+                        try {
+                            Thread.sleep(1000);
+                        } catch (InterruptedException iex) {
+                            Slog.e(TAG, "Interrupted while waiting for media", iex);
+                            break;
+                        }
+                        state = Environment.getExternalStorageState();
+                    }
+                    if (retries == 0) {
+                        Slog.e(TAG, "Timed out waiting for media to check");
+                    }
                 }
-                state = Environment.getExternalStorageState();
-            }
-            if (retries == 0) {
-                Slog.e(TAG, "Timed out waiting for media to check");
-            }
-        }
 
-        if (state.equals(Environment.MEDIA_MOUNTED)) {
-            // Post a unmount message.
-            ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
-            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
-        } else if (observer != null) {
-            /*
-             * Observer is waiting for onShutDownComplete when we are done.
-             * Since nothing will be done send notification directly so shutdown
-             * sequence can continue.
-             */
-            try {
-                observer.onShutDownComplete(StorageResultCode.OperationSucceeded);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException when shutting down");
+                if (state.equals(Environment.MEDIA_MOUNTED)) {
+                    // Post a unmount message.
+                    ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
+                    mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
+                } else if (observer != null) {
+                    /*
+                     * Observer is waiting for onShutDownComplete when we are done.
+                     * Since nothing will be done send notification directly so shutdown
+                     * sequence can continue.
+                     */
+                    try {
+                        observer.onShutDownComplete(StorageResultCode.OperationSucceeded);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException when shutting down");
+                    }
+                }
             }
         }
     }
@@ -1244,16 +1271,15 @@
      * @return state of the volume at the specified mount point
      */
     public String getVolumeState(String mountPoint) {
-        /*
-         * XXX: Until we have multiple volume discovery, just hardwire
-         * this to /sdcard
-         */
-        if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
-            Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
-            throw new IllegalArgumentException();
-        }
+        synchronized (mVolumeStates) {
+            String state = mVolumeStates.get(mountPoint);
+            if (state == null) {
+                Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
+                throw new IllegalArgumentException();
+            }
 
-        return mLegacyState;
+            return state;
+        }
     }
 
     public boolean isExternalStorageEmulated() {
@@ -1727,6 +1753,18 @@
         }
     }
 
+    public String[] getVolumeList() {
+        synchronized(mVolumeStates) {
+            Set<String> volumes = mVolumeStates.keySet();
+            String[] result = new String[volumes.size()];
+            int i = 0;
+            for (String volume : volumes) {
+                result[i++] = volume;
+            }
+            return result;
+        }
+    }
+
     private void addObbStateLocked(ObbState obbState) throws RemoteException {
         final IBinder binder = obbState.getBinder();
         List<ObbState> obbStates = mObbMounts.get(binder);
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index d542673..5cd942c 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -9323,6 +9323,19 @@
             }
             mPendingPackages.clear();
 
+            /*
+             * Make sure all the updated system packages have their shared users
+             * associated with them.
+             */
+            final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator();
+            while (disabledIt.hasNext()) {
+                final PackageSetting disabledPs = disabledIt.next();
+                final Object id = getUserIdLP(disabledPs.userId);
+                if (id != null && id instanceof SharedUserSetting) {
+                  disabledPs.sharedUser = (SharedUserSetting) id;
+                }
+            }
+
             readStoppedLP();
 
             mReadMessages.append("Read completed successfully: "
diff --git a/telephony/java/com/android/internal/telephony/ApnContext.java b/telephony/java/com/android/internal/telephony/ApnContext.java
index 3f1ca9e..010d61d 100644
--- a/telephony/java/com/android/internal/telephony/ApnContext.java
+++ b/telephony/java/com/android/internal/telephony/ApnContext.java
@@ -51,6 +51,8 @@
 
     DataConnection mDataConnection;
 
+    DataConnectionAc mDataConnectionAc;
+
     String mReason;
 
     PendingIntent mReconnectIntent;
@@ -96,6 +98,17 @@
         mDataConnection = dataConnection;
     }
 
+
+    public synchronized DataConnectionAc getDataConnectionAc() {
+        log("getDataConnectionAc dcac=" + mDataConnectionAc);
+        return mDataConnectionAc;
+    }
+
+    public synchronized void setDataConnectionAc(DataConnectionAc dcac) {
+        log("setDataConnectionAc dcac=" + dcac);
+        mDataConnectionAc = dcac;
+    }
+
     public synchronized ApnSetting getApnSetting() {
         return mApnSetting;
     }
@@ -206,6 +219,11 @@
        return mDependencyMet.get();
     }
 
+    @Override
+    public String toString() {
+        return "state=" + getState() + " apnType=" + mApnType;
+    }
+
     protected void log(String s) {
         Log.d(LOG_TAG, "[ApnContext] " + s);
     }
diff --git a/telephony/java/com/android/internal/telephony/DataCallState.java b/telephony/java/com/android/internal/telephony/DataCallState.java
index a883e8e..1d67d45 100644
--- a/telephony/java/com/android/internal/telephony/DataCallState.java
+++ b/telephony/java/com/android/internal/telephony/DataCallState.java
@@ -20,6 +20,7 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
+import android.net.RouteInfo;
 import android.os.SystemProperties;
 import android.util.Log;
 
@@ -196,7 +197,7 @@
                     } catch (IllegalArgumentException e) {
                         throw new UnknownHostException("Non-numeric gateway addr=" + addr);
                     }
-                    linkProperties.addGateway(ia);
+                    linkProperties.addRoute(new RouteInfo(ia));
                 }
 
                 result = SetupResult.SUCCESS;
@@ -223,5 +224,3 @@
         return result;
     }
 }
-
-
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 791fbfd..6a5b82c 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -17,22 +17,24 @@
 package com.android.internal.telephony;
 
 
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
-import android.net.LinkAddress;
 import android.net.LinkCapabilities;
 import android.net.LinkProperties;
-import android.net.NetworkUtils;
+import android.net.ProxyProperties;
 import android.os.AsyncResult;
+import android.os.Bundle;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 
-import java.net.InetAddress;
-import java.net.Inet4Address;
-import java.net.UnknownHostException;
 import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * {@hide}
@@ -60,6 +62,8 @@
 
     protected static Object mCountLock = new Object();
     protected static int mCount;
+    protected AsyncChannel mAc;
+
 
     /**
      * Used internally for saving connecting parameters.
@@ -76,13 +80,6 @@
     }
 
     /**
-     * An instance used for notification of blockingReset.
-     * TODO: Remove when blockingReset is removed.
-     */
-    class ResetSynchronouslyLock {
-    }
-
-    /**
      * Used internally for saving disconnecting parameters.
      */
     protected static class DisconnectParams {
@@ -90,15 +87,9 @@
             this.reason = reason;
             this.onCompletedMsg = onCompletedMsg;
         }
-        public DisconnectParams(ResetSynchronouslyLock lockObj) {
-            this.reason = null;
-            this.lockObj = lockObj;
-        }
-
         public int tag;
         public String reason;
         public Message onCompletedMsg;
-        public ResetSynchronouslyLock lockObj;
     }
 
     /**
@@ -188,13 +179,13 @@
     }
 
     // ***** Event codes for driving the state machine
-    protected static final int EVENT_RESET = 1;
-    protected static final int EVENT_CONNECT = 2;
-    protected static final int EVENT_SETUP_DATA_CONNECTION_DONE = 3;
-    protected static final int EVENT_GET_LAST_FAIL_DONE = 4;
-    protected static final int EVENT_DEACTIVATE_DONE = 5;
-    protected static final int EVENT_DISCONNECT = 6;
-    protected static final int EVENT_RIL_CONNECTED = 7;
+    protected static final int BASE = Protocol.BASE_DATA_CONNECTION;
+    protected static final int EVENT_CONNECT = BASE + 0;
+    protected static final int EVENT_SETUP_DATA_CONNECTION_DONE = BASE + 1;
+    protected static final int EVENT_GET_LAST_FAIL_DONE = BASE + 2;
+    protected static final int EVENT_DEACTIVATE_DONE = BASE + 3;
+    protected static final int EVENT_DISCONNECT = BASE + 4;
+    protected static final int EVENT_RIL_CONNECTED = BASE + 5;
 
     //***** Tag IDs for EventLog
     protected static final int EVENT_LOG_BAD_DNS_ADDRESS = 50100;
@@ -313,13 +304,8 @@
             AsyncResult.forMessage(msg);
             msg.sendToTarget();
         }
-        if (dp.lockObj != null) {
-            synchronized(dp.lockObj) {
-                dp.lockObj.notify();
-            }
-        }
-
         clearSettings();
+        if (DBG) log("NotifyDisconnectCompleted DisconnectParams=" + dp);
     }
 
     protected int getRadioTechnology(int defaultRadioTechnology) {
@@ -408,6 +394,49 @@
         return mRetryMgr.isRetryForever();
     }
 
+    private AtomicInteger mRefCount = new AtomicInteger(0);
+
+    /**
+     * Set refCount.
+     *
+     * @param val is new refCount
+     */
+    public void setRefCount(int val) {
+        mRefCount.set(val);
+    }
+
+    /**
+     * Get refCount
+     *
+     * @return refCount
+     */
+    public int getRefCount() {
+        return mRefCount.get();
+    }
+
+    /**
+     * @return decrement and return refCount
+     *
+     * TODO: Consider using the refCount for defining the
+     * life time of a connection. When this goes zero the
+     * DataConnection could tear itself down.
+     */
+    public int decAndGetRefCount() {
+        int v = mRefCount.decrementAndGet();
+        if (v < 0) {
+            log("BUG: decAndGetRefCount caused refCount to be < 0");
+            mRefCount.set(0);
+        }
+        return v;
+     }
+
+    /**
+     * @return increment and return refCount
+     */
+    public int incAndGetRefCount() {
+        return mRefCount.incrementAndGet();
+    }
+
     /*
      * **************************************************************************
      * End members owned by DataConnectionTracker
@@ -498,12 +527,74 @@
             AsyncResult ar;
 
             switch (msg.what) {
-                case EVENT_RESET:
-                    if (DBG) log("DcDefaultState: msg.what=EVENT_RESET");
-                    clearSettings();
-                    if (msg.obj != null) {
-                        notifyDisconnectCompleted((DisconnectParams) msg.obj);
+                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+                    if (mAc != null) {
+                        log("Disconnecting to previous connection mAc=" + mAc);
+                        mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
+                    } else {
+                        mAc = new AsyncChannel();
+                        mAc.connected(null, getHandler(), msg.replyTo);
+                        log("DcDefaultState: FULL_CONNECTION reply connected");
+                        mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                AsyncChannel.STATUS_SUCCESSFUL, mId, "hi");
                     }
+                    break;
+                }
+                case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+                    log("CMD_CHANNEL_DISCONNECT");
+                    mAc.disconnect();
+                    break;
+                }
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+                    log("CMD_CHANNEL_DISCONNECTED");
+                    mAc = null;
+                    break;
+                }
+                case DataConnectionAc.REQ_IS_INACTIVE: {
+                    boolean val = getCurrentState() == mInactiveState;
+                    log("REQ_IS_INACTIVE  isInactive=" + val);
+                    mAc.replyToMessage(msg, DataConnectionAc.RSP_IS_INACTIVE, val ? 1 : 0);
+                    break;
+                }
+                case DataConnectionAc.REQ_GET_CID: {
+                    log("REQ_GET_CID  cid=" + cid);
+                    mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_CID, cid);
+                    break;
+                }
+                case DataConnectionAc.REQ_GET_APNSETTING: {
+                    log("REQ_GET_APNSETTING  apnSetting=" + mApn);
+                    mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_APNSETTING, mApn);
+                    break;
+                }
+                case DataConnectionAc.REQ_GET_LINK_PROPERTIES: {
+                    LinkProperties lp = new LinkProperties(mLinkProperties);
+                    log("REQ_GET_LINK_PROPERTIES linkProperties" + lp);
+                    mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_LINK_PROPERTIES, lp);
+                    break;
+                }
+                case DataConnectionAc.REQ_SET_LINK_PROPERTIES_HTTP_PROXY: {
+                    ProxyProperties proxy = (ProxyProperties) msg.obj;
+                    log("REQ_SET_LINK_PROPERTIES_HTTP_PROXY proxy=" + proxy);
+                    mLinkProperties.setHttpProxy(proxy);
+                    mAc.replyToMessage(msg, DataConnectionAc.RSP_SET_LINK_PROPERTIES_HTTP_PROXY);
+                    break;
+                }
+                case DataConnectionAc.REQ_GET_LINK_CAPABILITIES: {
+                    LinkCapabilities lc = new LinkCapabilities(mCapabilities);
+                    log("REQ_GET_LINK_CAPABILITIES linkCapabilities" + lc);
+                    mAc.replyToMessage(msg, DataConnectionAc.RSP_GET_LINK_CAPABILITIES, lc);
+                    break;
+                }
+                case DataConnectionAc.REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE: {
+                    Bundle data = msg.getData();
+                    mLinkProperties = (LinkProperties) data.get("linkProperties");
+                    break;
+                }
+                case DataConnectionAc.REQ_RESET:
+                    if (DBG) log("DcDefaultState: msg.what=REQ_RESET");
+                    clearSettings();
+                    mAc.replyToMessage(msg, DataConnectionAc.RSP_RESET);
                     transitionTo(mInactiveState);
                     break;
 
@@ -539,7 +630,7 @@
                     break;
             }
 
-            return true;
+            return HANDLED;
         }
     }
     private DcDefaultState mDefaultState = new DcDefaultState();
@@ -597,14 +688,12 @@
             boolean retVal;
 
             switch (msg.what) {
-                case EVENT_RESET:
+                case DataConnectionAc.REQ_RESET:
                     if (DBG) {
-                        log("DcInactiveState: msg.what=EVENT_RESET, ignore we're already reset");
+                        log("DcInactiveState: msg.what=RSP_RESET, ignore we're already reset");
                     }
-                    if (msg.obj != null) {
-                        notifyDisconnectCompleted((DisconnectParams) msg.obj);
-                    }
-                    retVal = true;
+                    mAc.replyToMessage(msg, DataConnectionAc.RSP_RESET);
+                    retVal = HANDLED;
                     break;
 
                 case EVENT_CONNECT:
@@ -613,12 +702,12 @@
                     cp.tag = mTag;
                     onConnect(cp);
                     transitionTo(mActivatingState);
-                    retVal = true;
+                    retVal = HANDLED;
                     break;
 
                 default:
                     if (DBG) log("DcInactiveState nothandled msg.what=" + msg.what);
-                    retVal = false;
+                    retVal = NOT_HANDLED;
                     break;
             }
             return retVal;
@@ -640,7 +729,7 @@
                 case EVENT_DISCONNECT:
                     if (DBG) log("DcActivatingState deferring msg.what=EVENT_DISCONNECT");
                     deferMessage(msg);
-                    retVal = true;
+                    retVal = HANDLED;
                     break;
 
                 case EVENT_SETUP_DATA_CONNECTION_DONE:
@@ -685,7 +774,7 @@
                         default:
                             throw new RuntimeException("Unknown SetupResult, should not happen");
                     }
-                    retVal = true;
+                    retVal = HANDLED;
                     break;
 
                 case EVENT_GET_LAST_FAIL_DONE:
@@ -710,12 +799,12 @@
                         }
                     }
 
-                    retVal = true;
+                    retVal = HANDLED;
                     break;
 
                 default:
                     if (DBG) log("DcActivatingState not handled msg.what=" + msg.what);
-                    retVal = false;
+                    retVal = NOT_HANDLED;
                     break;
             }
             return retVal;
@@ -768,12 +857,12 @@
                     dp.tag = mTag;
                     tearDownData(dp);
                     transitionTo(mDisconnectingState);
-                    retVal = true;
+                    retVal = HANDLED;
                     break;
 
                 default:
                     if (DBG) log("DcActiveState nothandled msg.what=" + msg.what);
-                    retVal = false;
+                    retVal = NOT_HANDLED;
                     break;
             }
             return retVal;
@@ -803,12 +892,12 @@
                         if (DBG) log("DcDisconnectState EVENT_DEACTIVATE_DONE stale dp.tag="
                                 + dp.tag + " mTag=" + mTag);
                     }
-                    retVal = true;
+                    retVal = HANDLED;
                     break;
 
                 default:
                     if (DBG) log("DcDisconnectingState not handled msg.what=" + msg.what);
-                    retVal = false;
+                    retVal = NOT_HANDLED;
                     break;
             }
             return retVal;
@@ -845,7 +934,7 @@
                                     " stale dp.tag=" + cp.tag + ", mTag=" + mTag);
                         }
                     }
-                    retVal = true;
+                    retVal = HANDLED;
                     break;
 
                 default:
@@ -853,7 +942,7 @@
                         log("DcDisconnectionErrorCreatingConnection not handled msg.what="
                                 + msg.what);
                     }
-                    retVal = false;
+                    retVal = NOT_HANDLED;
                     break;
             }
             return retVal;
@@ -865,147 +954,26 @@
     // ******* public interface
 
     /**
-     * Disconnect from the network.
-     *
-     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
-     *        With AsyncResult.userObj set to the original msg.obj.
-     */
-    public void reset(Message onCompletedMsg) {
-        sendMessage(obtainMessage(EVENT_RESET, new DisconnectParams(null, onCompletedMsg)));
-    }
-
-    /**
-     * Reset the connection and wait for it to complete.
-     * TODO: Remove when all callers only need the asynchronous
-     * reset defined above.
-     */
-    public void resetSynchronously() {
-        ResetSynchronouslyLock lockObj = new ResetSynchronouslyLock();
-        synchronized(lockObj) {
-            sendMessage(obtainMessage(EVENT_RESET, new DisconnectParams(lockObj)));
-            try {
-                lockObj.wait();
-            } catch (InterruptedException e) {
-                log("blockingReset: unexpected interrupted of wait()");
-            }
-        }
-    }
-
-    /**
-     * Connect to the apn and return an AsyncResult in onCompletedMsg.
+     * Bring up a connection to the apn and return an AsyncResult in onCompletedMsg.
      * Used for cellular networks that use Acesss Point Names (APN) such
      * as GSM networks.
      *
      * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
      *        With AsyncResult.userObj set to the original msg.obj,
      *        AsyncResult.result = FailCause and AsyncResult.exception = Exception().
-     * @param apn is the Access Point Name to connect to
+     * @param apn is the Access Point Name to bring up a connection to
      */
-    public void connect(Message onCompletedMsg, ApnSetting apn) {
+    public void bringUp(Message onCompletedMsg, ApnSetting apn) {
         sendMessage(obtainMessage(EVENT_CONNECT, new ConnectionParams(apn, onCompletedMsg)));
     }
 
     /**
-     * Connect to the apn and return an AsyncResult in onCompletedMsg.
-     *
-     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
-     *        With AsyncResult.userObj set to the original msg.obj,
-     *        AsyncResult.result = FailCause and AsyncResult.exception = Exception().
-     */
-    public void connect(Message onCompletedMsg) {
-        sendMessage(obtainMessage(EVENT_CONNECT, new ConnectionParams(null, onCompletedMsg)));
-    }
-
-    /**
-     * Disconnect from the network.
+     * Tear down the connection through the apn on the network.
      *
      * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
      *        With AsyncResult.userObj set to the original msg.obj.
      */
-    public void disconnect(String reason, Message onCompletedMsg) {
+    public void tearDown(String reason, Message onCompletedMsg) {
         sendMessage(obtainMessage(EVENT_DISCONNECT, new DisconnectParams(reason, onCompletedMsg)));
     }
-
-    // ****** The following are used for debugging.
-
-    /**
-     * TODO: This should be an asynchronous call and we wouldn't
-     * have to use handle the notification in the DcInactiveState.enter.
-     *
-     * @return true if the state machine is in the inactive state.
-     */
-    public boolean isInactive() {
-        boolean retVal = getCurrentState() == mInactiveState;
-        return retVal;
-    }
-
-    /**
-     * TODO: This should be an asynchronous call and we wouldn't
-     * have to use handle the notification in the DcActiveState.enter.
-     *
-     * @return true if the state machine is in the active state.
-     */
-    public boolean isActive() {
-        boolean retVal = getCurrentState() == mActiveState;
-        return retVal;
-    }
-
-    /**
-     * Return the LinkProperties for the connection.
-     *
-     * @return a copy of the LinkProperties, is never null.
-     */
-    public LinkProperties getLinkProperties() {
-        return new LinkProperties(mLinkProperties);
-    }
-
-    /**
-     * A capability is an Integer/String pair, the capabilities
-     * are defined in the class LinkSocket#Key.
-     *
-     * @return a copy of this connections capabilities, may be empty but never null.
-     */
-    public LinkCapabilities getLinkCapabilities() {
-        return new LinkCapabilities(mCapabilities);
-    }
-
-    /**
-     * @return the current state as a string.
-     */
-    public String getStateAsString() {
-        String retVal = getCurrentState().getName();
-        return retVal;
-    }
-
-    /**
-     * @return the time of when this connection was created.
-     */
-    public long getConnectionTime() {
-        return createTime;
-    }
-
-    /**
-     * @return the time of the last failure.
-     */
-    public long getLastFailTime() {
-        return lastFailTime;
-    }
-
-    /**
-     * @return the last cause of failure.
-     */
-    public FailCause getLastFailCause() {
-        return lastFailCause;
-    }
-
-    /**
-     * @return the current ApnSetting
-     */
-    public ApnSetting getApn() {
-        return mApn;
-    }
-
-    public int getCid() {
-        return cid;
-    }
 }
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionAc.java b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
new file mode 100644
index 0000000..a9796dd
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
@@ -0,0 +1,288 @@
+/*
+ * 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 com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.net.ProxyProperties;
+import android.os.Message;
+
+/**
+ * AsyncChannel to a DataConnection
+ */
+public class DataConnectionAc extends AsyncChannel {
+    private static final boolean DBG = true;
+    private String mLogTag;
+
+    public DataConnection dataConnection;
+
+    public static final int BASE = Protocol.BASE_DATA_CONNECTION_AC;
+
+    public static final int REQ_IS_INACTIVE = BASE + 0;
+    public static final int RSP_IS_INACTIVE = BASE + 1;
+
+    public static final int REQ_GET_CID = BASE + 2;
+    public static final int RSP_GET_CID = BASE + 3;
+
+    public static final int REQ_GET_APNSETTING = BASE + 4;
+    public static final int RSP_GET_APNSETTING = BASE + 5;
+
+    public static final int REQ_GET_LINK_PROPERTIES = BASE + 6;
+    public static final int RSP_GET_LINK_PROPERTIES = BASE + 7;
+
+    public static final int REQ_SET_LINK_PROPERTIES_HTTP_PROXY = BASE + 8;
+    public static final int RSP_SET_LINK_PROPERTIES_HTTP_PROXY = BASE + 9;
+
+    public static final int REQ_GET_LINK_CAPABILITIES = BASE + 10;
+    public static final int RSP_GET_LINK_CAPABILITIES = BASE + 11;
+
+    public static final int REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE = BASE + 12;
+    public static final int RSP_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE = BASE + 13;
+
+    public static final int REQ_RESET = BASE + 14;
+    public static final int RSP_RESET = BASE + 15;
+
+    public DataConnectionAc(DataConnection dc, String logTag) {
+        dataConnection = dc;
+        mLogTag = logTag;
+    }
+
+    /**
+     * Request if the state machine is in the inactive state.
+     * Response {@link #rspIsInactive}
+     */
+    public void reqIsInactive() {
+        sendMessage(REQ_IS_INACTIVE);
+        if (DBG) log("reqIsInactive");
+    }
+
+    /**
+     * Evaluate RSP_IS_INACTIVE.
+     *
+     * @return true if the state machine is in the inactive state.
+     */
+    public boolean rspIsInactive(Message response) {
+        boolean retVal = response.arg1 == 1;
+        if (DBG) log("rspIsInactive=" + retVal);
+        return retVal;
+    }
+
+    /**
+     * @return true if the state machine is in the inactive state.
+     */
+    public boolean isInactiveSync() {
+        Message response = sendMessageSynchronously(REQ_IS_INACTIVE);
+        if ((response != null) && (response.what == RSP_IS_INACTIVE)) {
+            return rspIsInactive(response);
+        } else {
+            log("rspIsInactive error response=" + response);
+            return false;
+        }
+    }
+
+    /**
+     * Request the Connection ID.
+     * Response {@link #rspCid}
+     */
+    public void reqCid() {
+        sendMessage(REQ_GET_CID);
+        if (DBG) log("reqCid");
+    }
+
+    /**
+     * Evaluate a RSP_GET_CID message and return the cid.
+     *
+     * @param response Message
+     * @return connection id or -1 if an error
+     */
+    public int rspCid(Message response) {
+        int retVal = response.arg1;
+        if (DBG) log("rspCid=" + retVal);
+        return retVal;
+    }
+
+    /**
+     * @return connection id or -1 if an error
+     */
+    public int getCidSync() {
+        Message response = sendMessageSynchronously(REQ_GET_CID);
+        if ((response != null) && (response.what == RSP_GET_CID)) {
+            return rspCid(response);
+        } else {
+            log("rspCid error response=" + response);
+            return -1;
+        }
+    }
+
+    /**
+     * Request the connections ApnSetting.
+     * Response {@link #rspApnSetting}
+     */
+    public void reqApnSetting() {
+        sendMessage(REQ_GET_APNSETTING);
+        if (DBG) log("reqApnSetting");
+    }
+
+    /**
+     * Evaluate a RSP_APN_SETTING message and return the ApnSetting.
+     *
+     * @param response Message
+     * @return ApnSetting, maybe null
+     */
+    public ApnSetting rspApnSetting(Message response) {
+        ApnSetting retVal = (ApnSetting) response.obj;
+        if (DBG) log("rspApnSetting=" + retVal);
+        return retVal;
+    }
+
+    /**
+     * Get the connections ApnSetting.
+     *
+     * @return ApnSetting or null if an error
+     */
+    public ApnSetting getApnSettingSync() {
+        Message response = sendMessageSynchronously(REQ_GET_APNSETTING);
+        if ((response != null) && (response.what == RSP_GET_APNSETTING)) {
+            return rspApnSetting(response);
+        } else {
+            log("getApnSetting error response=" + response);
+            return null;
+        }
+    }
+
+    /**
+     * Request the connections LinkProperties.
+     * Response {@link #rspLinkProperties}
+     */
+    public void reqLinkProperties() {
+        sendMessage(REQ_GET_LINK_PROPERTIES);
+        if (DBG) log("reqLinkProperties");
+    }
+
+    /**
+     * Evaluate RSP_GET_LINK_PROPERTIES
+     *
+     * @param response
+     * @return LinkProperties, maybe null.
+     */
+    public LinkProperties rspLinkProperties(Message response) {
+        LinkProperties retVal = (LinkProperties) response.obj;
+        if (DBG) log("rspLinkProperties=" + retVal);
+        return retVal;
+    }
+
+    /**
+     * Get the connections LinkProperties.
+     *
+     * @return LinkProperties or null if an error
+     */
+    public LinkProperties getLinkPropertiesSync() {
+        Message response = sendMessageSynchronously(REQ_GET_LINK_PROPERTIES);
+        if ((response != null) && (response.what == RSP_GET_LINK_PROPERTIES)) {
+            return rspLinkProperties(response);
+        } else {
+            log("getLinkProperties error response=" + response);
+            return null;
+        }
+    }
+
+    /**
+     * Request setting the connections LinkProperties.HttpProxy.
+     * Response RSP_SET_LINK_PROPERTIES when complete.
+     */
+    public void reqSetLinkPropertiesHttpProxy(ProxyProperties proxy) {
+        sendMessage(REQ_SET_LINK_PROPERTIES_HTTP_PROXY, proxy);
+        if (DBG) log("reqSetLinkPropertiesHttpProxy proxy=" + proxy);
+    }
+
+    /**
+     * Set the connections LinkProperties.HttpProxy
+     */
+    public void setLinkPropertiesHttpProxySync(ProxyProperties proxy) {
+        Message response =
+            sendMessageSynchronously(REQ_SET_LINK_PROPERTIES_HTTP_PROXY, proxy);
+        if ((response != null) && (response.what == RSP_SET_LINK_PROPERTIES_HTTP_PROXY)) {
+            if (DBG) log("setLinkPropertiesHttpPoxy ok");
+        } else {
+            log("setLinkPropertiesHttpPoxy error response=" + response);
+        }
+    }
+
+    /**
+     * Request the connections LinkCapabilities.
+     * Response {@link #rspLinkCapabilities}
+     */
+    public void reqLinkCapabilities() {
+        sendMessage(REQ_GET_LINK_CAPABILITIES);
+        if (DBG) log("reqLinkCapabilities");
+    }
+
+    /**
+     * Evaluate RSP_GET_LINK_CAPABILITIES
+     *
+     * @param response
+     * @return LinkCapabilites, maybe null.
+     */
+    public LinkCapabilities rspLinkCapabilities(Message response) {
+        LinkCapabilities retVal = (LinkCapabilities) response.obj;
+        if (DBG) log("rspLinkCapabilities=" + retVal);
+        return retVal;
+    }
+
+    /**
+     * Get the connections LinkCapabilities.
+     *
+     * @return LinkCapabilities or null if an error
+     */
+    public LinkCapabilities getLinkCapabilitiesSync() {
+        Message response = sendMessageSynchronously(REQ_GET_LINK_CAPABILITIES);
+        if ((response != null) && (response.what == RSP_GET_LINK_CAPABILITIES)) {
+            return rspLinkCapabilities(response);
+        } else {
+            log("getLinkCapabilities error response=" + response);
+            return null;
+        }
+    }
+
+    /**
+     * Request the connections LinkCapabilities.
+     * Response RSP_RESET when complete
+     */
+    public void reqReset() {
+        sendMessage(REQ_RESET);
+        if (DBG) log("reqReset");
+    }
+
+    /**
+     * Reset the connection and wait for it to complete.
+     */
+    public void resetSync() {
+        Message response = sendMessageSynchronously(REQ_RESET);
+        if ((response != null) && (response.what == RSP_RESET)) {
+            if (DBG) log("restSync ok");
+        } else {
+            if (DBG) log("restSync error response=" + response);
+        }
+    }
+
+    private void log(String s) {
+        android.util.Log.d(mLogTag, "DataConnectionAc " + s);
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 01ac95f..ad4e796 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -22,7 +22,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
-import android.net.IConnectivityManager;
 import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
@@ -32,7 +31,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
-import android.os.ServiceManager;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -40,6 +38,8 @@
 import android.util.Log;
 
 import com.android.internal.R;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -91,38 +91,39 @@
     public static String EXTRA_MESSENGER = "EXTRA_MESSENGER";
 
     /***** Event Codes *****/
-    protected static final int EVENT_DATA_SETUP_COMPLETE = 1;
-    protected static final int EVENT_RADIO_AVAILABLE = 3;
-    protected static final int EVENT_RECORDS_LOADED = 4;
-    protected static final int EVENT_TRY_SETUP_DATA = 5;
-    protected static final int EVENT_DATA_STATE_CHANGED = 6;
-    protected static final int EVENT_POLL_PDP = 7;
-    protected static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 12;
-    protected static final int EVENT_VOICE_CALL_STARTED = 14;
-    protected static final int EVENT_VOICE_CALL_ENDED = 15;
-    protected static final int EVENT_DATA_CONNECTION_DETACHED = 19;
-    protected static final int EVENT_LINK_STATE_CHANGED = 20;
-    protected static final int EVENT_ROAMING_ON = 21;
-    protected static final int EVENT_ROAMING_OFF = 22;
-    protected static final int EVENT_ENABLE_NEW_APN = 23;
-    protected static final int EVENT_RESTORE_DEFAULT_APN = 24;
-    protected static final int EVENT_DISCONNECT_DONE = 25;
-    protected static final int EVENT_DATA_CONNECTION_ATTACHED = 26;
-    protected static final int EVENT_START_NETSTAT_POLL = 27;
-    protected static final int EVENT_START_RECOVERY = 28;
-    protected static final int EVENT_APN_CHANGED = 29;
-    protected static final int EVENT_CDMA_DATA_DETACHED = 30;
-    protected static final int EVENT_NV_READY = 31;
-    protected static final int EVENT_PS_RESTRICT_ENABLED = 32;
-    protected static final int EVENT_PS_RESTRICT_DISABLED = 33;
-    public static final int EVENT_CLEAN_UP_CONNECTION = 34;
-    protected static final int EVENT_CDMA_OTA_PROVISION = 35;
-    protected static final int EVENT_RESTART_RADIO = 36;
-    protected static final int EVENT_SET_INTERNAL_DATA_ENABLE = 37;
-    protected static final int EVENT_RESET_DONE = 38;
-    public static final int CMD_SET_DATA_ENABLE = 39;
-    public static final int EVENT_CLEAN_UP_ALL_CONNECTIONS = 40;
-    public static final int CMD_SET_DEPENDENCY_MET = 41;
+    protected static final int BASE = Protocol.BASE_DATA_CONNECTION_TRACKER;
+    protected static final int EVENT_DATA_SETUP_COMPLETE = BASE + 0;
+    protected static final int EVENT_RADIO_AVAILABLE = BASE + 1;
+    protected static final int EVENT_RECORDS_LOADED = BASE + 2;
+    protected static final int EVENT_TRY_SETUP_DATA = BASE + 3;
+    protected static final int EVENT_DATA_STATE_CHANGED = BASE + 4;
+    protected static final int EVENT_POLL_PDP = BASE + 5;
+    protected static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = BASE + 6;
+    protected static final int EVENT_VOICE_CALL_STARTED = BASE + 7;
+    protected static final int EVENT_VOICE_CALL_ENDED = BASE + 8;
+    protected static final int EVENT_DATA_CONNECTION_DETACHED = BASE + 9;
+    protected static final int EVENT_LINK_STATE_CHANGED = BASE + 10;
+    protected static final int EVENT_ROAMING_ON = BASE + 11;
+    protected static final int EVENT_ROAMING_OFF = BASE + 12;
+    protected static final int EVENT_ENABLE_NEW_APN = BASE + 13;
+    protected static final int EVENT_RESTORE_DEFAULT_APN = BASE + 14;
+    protected static final int EVENT_DISCONNECT_DONE = BASE + 15;
+    protected static final int EVENT_DATA_CONNECTION_ATTACHED = BASE + 16;
+    protected static final int EVENT_START_NETSTAT_POLL = BASE + 17;
+    protected static final int EVENT_START_RECOVERY = BASE + 18;
+    protected static final int EVENT_APN_CHANGED = BASE + 19;
+    protected static final int EVENT_CDMA_DATA_DETACHED = BASE + 20;
+    protected static final int EVENT_NV_READY = BASE + 21;
+    protected static final int EVENT_PS_RESTRICT_ENABLED = BASE + 22;
+    protected static final int EVENT_PS_RESTRICT_DISABLED = BASE + 23;
+    public static final int EVENT_CLEAN_UP_CONNECTION = BASE + 24;
+    protected static final int EVENT_CDMA_OTA_PROVISION = BASE + 25;
+    protected static final int EVENT_RESTART_RADIO = BASE + 26;
+    protected static final int EVENT_SET_INTERNAL_DATA_ENABLE = BASE + 27;
+    protected static final int EVENT_RESET_DONE = BASE + 28;
+    public static final int CMD_SET_DATA_ENABLE = BASE + 29;
+    public static final int EVENT_CLEAN_UP_ALL_CONNECTIONS = BASE + 30;
+    public static final int CMD_SET_DEPENDENCY_MET = BASE + 31;
 
     /***** Constants *****/
 
@@ -227,7 +228,7 @@
     /** indication of our availability (preconditions to trysetupData are met) **/
     protected boolean mAvailability = false;
 
-    // When false we will not auto attach and manully attaching is required.
+    // When false we will not auto attach and manually attaching is required.
     protected boolean mAutoAttachOnCreation = false;
 
     // State of screen
@@ -235,12 +236,6 @@
     //        really a lower power mode")
     protected boolean mIsScreenOn = true;
 
-    /** The link properties (dns, gateway, ip, etc) */
-    protected LinkProperties mLinkProperties = new LinkProperties();
-
-    /** The link capabilities */
-    protected LinkCapabilities mLinkCapabilities = new LinkCapabilities();
-
     /** Allows the generation of unique Id's for DataConnection objects */
     protected AtomicInteger mUniqueIdGenerator = new AtomicInteger(0);
 
@@ -248,6 +243,10 @@
     protected HashMap<Integer, DataConnection> mDataConnections =
         new HashMap<Integer, DataConnection>();
 
+    /** The data connection async channels */
+    protected HashMap<Integer, DataConnectionAc> mDataConnectionAsyncChannels =
+        new HashMap<Integer, DataConnectionAc>();
+
     /** Convert an ApnType string to Id (TODO: Use "enumeration" instead of String for ApnType) */
     protected HashMap<String, Integer> mApnToDataConnectionId =
                                     new HashMap<String, Integer>();
@@ -267,7 +266,6 @@
     /** Is packet service restricted by network */
     protected boolean mIsPsRestricted = false;
 
-
     /* Once disposed dont handle any messages */
     protected boolean mIsDisposed = false;
 
@@ -351,6 +349,10 @@
     }
 
     public void dispose() {
+        for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+            dcac.disconnect();
+        }
+        mDataConnectionAsyncChannels.clear();
         mIsDisposed = true;
         mPhone.getContext().unregisterReceiver(this.mIntentReceiver);
     }
@@ -463,7 +465,13 @@
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-
+            case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+                log("DISCONNECTED_CONNECTED: msg=" + msg);
+                DataConnectionAc dcac = (DataConnectionAc) msg.obj;
+                mDataConnectionAsyncChannels.remove(dcac.dataConnection.getDataConnectionId());
+                dcac.disconnected();
+                break;
+            }
             case EVENT_ENABLE_NEW_APN:
                 onEnableApn(msg.arg1, msg.arg2);
                 break;
@@ -528,19 +536,20 @@
                 break;
             }
             case EVENT_RESET_DONE: {
+                if (DBG) log("EVENT_RESET_DONE");
                 onResetDone((AsyncResult) msg.obj);
                 break;
             }
             case CMD_SET_DATA_ENABLE: {
-                log("CMD_SET_DATA_ENABLE msg=" + msg);
                 boolean enabled = (msg.arg1 == ENABLED) ? true : false;
+                if (DBG) log("CMD_SET_DATA_ENABLE enabled=" + enabled);
                 onSetDataEnabled(enabled);
                 break;
             }
 
             case CMD_SET_DEPENDENCY_MET: {
-                log("CMD_SET_DEPENDENCY_MET msg=" + msg);
                 boolean met = (msg.arg1 == ENABLED) ? true : false;
+                if (DBG) log("CMD_SET_DEPENDENCY_MET met=" + met);
                 Bundle bundle = msg.getData();
                 if (bundle != null) {
                     String apnType = (String)bundle.get(APN_TYPE_KEY);
@@ -552,7 +561,7 @@
             }
 
             default:
-                Log.e("DATA", "Unidentified event = " + msg.what);
+                Log.e("DATA", "Unidentified event msg=" + msg);
                 break;
         }
     }
@@ -618,7 +627,8 @@
     protected LinkProperties getLinkProperties(String apnType) {
         int id = apnTypeToId(apnType);
         if (isApnIdEnabled(id)) {
-            return new LinkProperties(mLinkProperties);
+            DataConnectionAc dcac = mDataConnectionAsyncChannels.get(id);
+            return dcac.getLinkPropertiesSync();
         } else {
             return new LinkProperties();
         }
@@ -627,33 +637,13 @@
     protected LinkCapabilities getLinkCapabilities(String apnType) {
         int id = apnTypeToId(apnType);
         if (isApnIdEnabled(id)) {
-            return new LinkCapabilities(mLinkCapabilities);
+            DataConnectionAc dcac = mDataConnectionAsyncChannels.get(id);
+            return dcac.getLinkCapabilitiesSync();
         } else {
             return new LinkCapabilities();
         }
     }
 
-    /**
-     * Return the LinkProperties for the connection.
-     *
-     * @param connection
-     * @return a copy of the LinkProperties, is never null.
-     */
-    protected LinkProperties getLinkProperties(DataConnection connection) {
-        return connection.getLinkProperties();
-    }
-
-    /**
-     * A capability is an Integer/String pair, the capabilities
-     * are defined in the class LinkSocket#Key.
-     *
-     * @param connection
-     * @return a copy of this connections capabilities, may be empty but never null.
-     */
-    protected LinkCapabilities getLinkCapabilities(DataConnection connection) {
-        return connection.getLinkCapabilities();
-    }
-
     // tell all active apns of the current condition
     protected void notifyDataConnection(String reason) {
         for (int id = 0; id < APN_NUM_TYPES; id++) {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java
index e299d4a..d55f346 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java
@@ -20,7 +20,6 @@
 import android.util.Log;
 
 import com.android.internal.telephony.DataConnection;
-import com.android.internal.telephony.DataConnection.FailCause;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.RetryManager;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index dc85017..4b185a0 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -37,10 +37,12 @@
 import com.android.internal.telephony.DataCallState;
 import com.android.internal.telephony.DataConnection.FailCause;
 import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.DataConnectionAc;
 import com.android.internal.telephony.DataConnectionTracker;
 import com.android.internal.telephony.EventLogTags;
 import com.android.internal.telephony.RetryManager;
 import com.android.internal.telephony.Phone;
+import com.android.internal.util.AsyncChannel;
 
 import java.util.ArrayList;
 
@@ -297,14 +299,18 @@
         boolean notificationDeferred = false;
         for (DataConnection conn : mDataConnections.values()) {
             if(conn != null) {
+                DataConnectionAc dcac =
+                    mDataConnectionAsyncChannels.get(conn.getDataConnectionId());
                 if (tearDown) {
                     if (DBG) log("cleanUpConnection: teardown, call conn.disconnect");
-                    conn.disconnect(reason, obtainMessage(EVENT_DISCONNECT_DONE,
+                    conn.tearDown(reason, obtainMessage(EVENT_DISCONNECT_DONE,
                             conn.getDataConnectionId(), 0, reason));
                     notificationDeferred = true;
                 } else {
                     if (DBG) log("cleanUpConnection: !tearDown, call conn.resetSynchronously");
-                    conn.resetSynchronously();
+                    if (dcac != null) {
+                        dcac.resetSync();
+                    }
                     notificationDeferred = false;
                 }
             }
@@ -319,11 +325,13 @@
     }
 
     private CdmaDataConnection findFreeDataConnection() {
-        for (DataConnection dc : mDataConnections.values()) {
-            if (dc.isInactive()) {
-                return (CdmaDataConnection) dc;
+        for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+            if (dcac.isInactiveSync()) {
+                log("found free GsmDataConnection");
+                return (CdmaDataConnection) dcac.dataConnection;
             }
         }
+        log("NO free CdmaDataConnection");
         return null;
     }
 
@@ -349,12 +357,12 @@
         }
         mActiveApn = new ApnSetting(apnId, "", "", "", "", "", "", "", "", "",
                                     "", 0, types, "IP", "IP");
-        if (DBG) log("setupData: mActiveApn=" + mActiveApn);
+        if (DBG) log("call conn.bringUp mActiveApn=" + mActiveApn);
 
         Message msg = obtainMessage();
         msg.what = EVENT_DATA_SETUP_COMPLETE;
         msg.obj = reason;
-        conn.connect(msg, mActiveApn);
+        conn.bringUp(msg, mActiveApn);
 
         setState(State.INITING);
         notifyDataConnection(reason);
@@ -653,11 +661,7 @@
         }
 
         if (ar.exception == null) {
-            // TODO: We should clear LinkProperties/Capabilities when torn down or disconnected
-            mLinkProperties = getLinkProperties(mPendingDataConnection);
-            mLinkCapabilities = getLinkCapabilities(mPendingDataConnection);
-
-            // everything is setup
+            // Everything is setup
             notifyDefaultData(reason);
         } else {
             FailCause cause = (FailCause) (ar.result);
@@ -767,6 +771,16 @@
             int id = mUniqueIdGenerator.getAndIncrement();
             dataConn = CdmaDataConnection.makeDataConnection(mCdmaPhone, id, rm);
             mDataConnections.put(id, dataConn);
+            DataConnectionAc dcac = new DataConnectionAc(dataConn, LOG_TAG);
+            int status = dcac.fullyConnectSync(mPhone.getContext(), this, dataConn.getHandler());
+            if (status == AsyncChannel.STATUS_SUCCESSFUL) {
+                log("Fully connected");
+                mDataConnectionAsyncChannels.put(dcac.dataConnection.getDataConnectionId(), dcac);
+            } else {
+                log("Could not connect to dcac.dataConnection=" + dcac.dataConnection +
+                        " status=" + status);
+            }
+
         }
     }
 
@@ -897,6 +911,7 @@
 
     @Override
     public void handleMessage (Message msg) {
+        if (DBG) log("CdmaDCT handleMessage msg=" + msg);
 
         if (!mPhone.mIsTheCurrentActivePhone || mIsDisposed) {
             log("Ignore CDMA msgs since CDMA phone is inactive");
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
index 545ad8a..9695344 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
@@ -21,7 +21,6 @@
 import android.util.Patterns;
 import android.text.TextUtils;
 
-import com.android.internal.telephony.ApnSetting;
 import com.android.internal.telephony.DataConnection;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 8e675fc..a1b4376 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -53,6 +53,7 @@
 import com.android.internal.telephony.ApnSetting;
 import com.android.internal.telephony.DataCallState;
 import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.DataConnectionAc;
 import com.android.internal.telephony.DataConnectionTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
@@ -60,6 +61,7 @@
 import com.android.internal.telephony.EventLogTags;
 import com.android.internal.telephony.DataConnection.FailCause;
 import com.android.internal.telephony.RILConstants;
+import com.android.internal.util.AsyncChannel;
 
 import java.io.IOException;
 import java.net.InetAddress;
@@ -119,7 +121,7 @@
 
     @Override
     protected void onActionIntentReconnectAlarm(Intent intent) {
-        log("GPRS reconnect alarm. Previous state was " + mState);
+        if (DBG) log("GPRS reconnect alarm. Previous state was " + mState);
 
         String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON);
         String type = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE);
@@ -224,7 +226,7 @@
         boolean possible = (isDataAllowed()
                 && !(getAnyDataEnabled() && (getOverallState() == State.FAILED)));
         if (!possible && DBG && isDataAllowed()) {
-            log("Data not possible.  No coverage: dataState = " + getOverallState());
+            if (DBG) log("Data not possible.  No coverage: dataState = " + getOverallState());
         }
         return possible;
     }
@@ -319,10 +321,10 @@
     protected LinkProperties getLinkProperties(String apnType) {
         ApnContext apnContext = mApnContexts.get(apnType);
         if (apnContext != null) {
-            DataConnection dataConnection = apnContext.getDataConnection();
-            if (dataConnection != null) {
-                if (DBG) log("get active pdp is not null, return link properites for " + apnType);
-                return dataConnection.getLinkProperties();
+            DataConnectionAc dcac = apnContext.getDataConnectionAc();
+            if (dcac != null) {
+                if (DBG) log("return link properites for " + apnType);
+                return dcac.getLinkPropertiesSync();
             }
         }
         if (DBG) log("return new LinkProperties");
@@ -333,10 +335,10 @@
     protected LinkCapabilities getLinkCapabilities(String apnType) {
         ApnContext apnContext = mApnContexts.get(apnType);
         if (apnContext!=null) {
-            DataConnection dataConnection = apnContext.getDataConnection();
-            if (dataConnection != null) {
+            DataConnectionAc dataConnectionAc = apnContext.getDataConnectionAc();
+            if (dataConnectionAc != null) {
                 if (DBG) log("get active pdp is not null, return link Capabilities for " + apnType);
-                return dataConnection.getLinkCapabilities();
+                return dataConnectionAc.getLinkCapabilitiesSync();
             }
         }
         if (DBG) log("return new LinkCapabilities");
@@ -424,6 +426,7 @@
         }
 
         if (!isAnyEnabled) { // Nothing enabled. return IDLE.
+            if (DBG) log( "overall state is IDLE");
             return State.IDLE;
         }
 
@@ -450,34 +453,34 @@
      */
     @Override
     public synchronized int enableApnType(String apnType) {
-        if (DBG) log("calling enableApnType with type:" + apnType);
-
         ApnContext apnContext = mApnContexts.get(apnType);
         if (apnContext == null || !isApnTypeAvailable(apnType)) {
-            if (DBG) log("type not available");
+            if (DBG) log("enableApnType: " + apnType + " is type not available");
             return Phone.APN_TYPE_NOT_AVAILABLE;
         }
 
         // If already active, return
-        log("enableApnType(" + apnType + ")" + ", mState(" + apnContext.getState() + ")");
+        if (DBG) log("enableApnType: " + apnType + " mState(" + apnContext.getState() + ")");
 
         if (apnContext.getState() == State.INITING) {
-            if (DBG) log("return APN_REQUEST_STARTED");
+            if (DBG) log("enableApnType: return APN_REQUEST_STARTED");
             return Phone.APN_REQUEST_STARTED;
         }
         else if (apnContext.getState() == State.CONNECTED) {
-            if (DBG) log("return APN_ALREADY_ACTIVE");
+            if (DBG) log("enableApnType: return APN_ALREADY_ACTIVE");
             return Phone.APN_ALREADY_ACTIVE;
         }
         else if (apnContext.getState() == State.DISCONNECTING) {
-            if (DBG) log("requested APN while disconnecting");
+            if (DBG) log("enableApnType: while disconnecting, return APN_REQUEST_STARTED");
             apnContext.setPendingAction(ApnContext.PENDING_ACTION_RECONNECT);
             return Phone.APN_REQUEST_STARTED;
         }
 
-        if (DBG) log("new apn request for type " + apnType + " is to be handled");
         setEnabled(apnTypeToId(apnType), true);
-        if (DBG) log("return APN_REQUEST_STARTED");
+        if (DBG) {
+            log("enableApnType: new apn request for type " + apnType +
+                    " return APN_REQUEST_STARTED");
+        }
         return Phone.APN_REQUEST_STARTED;
     }
 
@@ -502,7 +505,7 @@
 
     @Override
     public synchronized int disableApnType(String type) {
-        if (DBG) log("calling disableApnType with type:" + type);
+        if (DBG) log("disableApnType:" + type);
         ApnContext apnContext = mApnContexts.get(type);
 
         if (apnContext != null) {
@@ -515,18 +518,19 @@
                 apnContext.setReason(Phone.REASON_DATA_DISABLED);
                 msg.obj = apnContext;
                 sendMessage(msg);
-                if (DBG) log("return APN_REQUEST_STARTED");
+                if (DBG) log("diableApnType: return APN_REQUEST_STARTED");
                 return Phone.APN_REQUEST_STARTED;
             } else {
-                if (DBG) log("return APN_ALREADY_INACTIVE");
+                if (DBG) log("disableApnType: return APN_ALREADY_INACTIVE");
                 apnContext.setEnabled(false);
                 apnContext.setDataConnection(null);
                 return Phone.APN_ALREADY_INACTIVE;
             }
 
         } else {
-            if (DBG)
-                log("no apn context was found, return APN_REQUEST_FAILED");
+            if (DBG) {
+                log("disableApnType: no apn context was found, return APN_REQUEST_FAILED");
+            }
             return Phone.APN_REQUEST_FAILED;
         }
     }
@@ -583,12 +587,15 @@
          * We presently believe it is unnecessary to tear down the PDP context
          * when GPRS detaches, but we should stop the network polling.
          */
+        if (DBG) log ("onDataConnectionDetached: stop polling and notify detached");
         stopNetStatPoll();
         notifyDataConnection(Phone.REASON_DATA_DETACHED);
     }
 
     private void onDataConnectionAttached() {
+        if (DBG) log("onDataConnectionAttached");
         if (getOverallState() == State.CONNECTED) {
+            if (DBG) log("onDataConnectionAttached: start polling notify attached");
             startNetStatPoll();
             notifyDataConnection(Phone.REASON_DATA_ATTACHED);
         }
@@ -624,11 +631,40 @@
             }
             if (mIsPsRestricted) reason += " - mIsPsRestricted= true";
             if (!desiredPowerState) reason += " - desiredPowerState= false";
-            log("Data not allowed due to" + reason);
+            if (DBG) log("isDataAllowed: not allowed due to" + reason);
         }
         return allowed;
     }
 
+    /**
+     * Release the apnContext
+     *
+     * @param apnContext
+     * @param tearDown
+     * @return refCount
+     */
+    private int releaseApnContext(ApnContext apnContext, boolean tearDown) {
+        if (apnContext == null) {
+            if (DBG) loge("releaseApnContext: apnContext null should not happen, ignore");
+            return -1;
+        }
+        DataConnection dc = apnContext.getDataConnection();
+        if (dc == null) {
+            if (DBG) loge("releaseApnContext: apnContext dc == null should not happen, ignore");
+            return -1;
+        }
+        int refCount = dc.decAndGetRefCount();
+        if (DBG) log("releaseApnContext: dec refCount=" + refCount + " tearDown=" + tearDown);
+        if (tearDown && (refCount == 0)) {
+            if (DBG) log("releaseApnContext: tearing down");
+            Message msg = obtainMessage(EVENT_DISCONNECT_DONE, apnContext);
+            apnContext.getDataConnection().tearDown(apnContext.getReason(), msg);
+        }
+        apnContext.setDataConnection(null);
+        apnContext.setDataConnectionAc(null);
+        return refCount;
+    }
+
     private void setupDataOnReadyApns(String reason) {
         // Only check for default APN state
         for (ApnContext apnContext : mApnContexts.values()) {
@@ -651,9 +687,8 @@
 
     private boolean trySetupData(String reason, String type) {
         if (DBG) {
-            log("***trySetupData for type:" + type +
-                    " due to " + (reason == null ? "(unspecified)" : reason) +
-                    " isPsRestricted=" + mIsPsRestricted);
+            log("trySetupData: " + type + " due to " + (reason == null ? "(unspecified)" : reason)
+                    + " isPsRestricted=" + mIsPsRestricted);
         }
 
         if (type == null) {
@@ -663,18 +698,16 @@
         ApnContext apnContext = mApnContexts.get(type);
 
         if (apnContext == null ){
-            if (DBG) log("new apn context for type:" + type);
+            if (DBG) log("trySetupData new apn context for type:" + type);
             apnContext = new ApnContext(type, LOG_TAG);
             mApnContexts.put(type, apnContext);
         }
         apnContext.setReason(reason);
 
         return trySetupData(apnContext);
-
     }
 
     private boolean trySetupData(ApnContext apnContext) {
-
         if (DBG) {
             log("trySetupData for type:" + apnContext.getApnType() +
                     " due to " + apnContext.getReason());
@@ -687,7 +720,7 @@
             apnContext.setState(State.CONNECTED);
             mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
 
-            log("(fix?) We're on the simulator; assuming data is connected");
+            log("trySetupData: (fix?) We're on the simulator; assuming data is connected");
             return true;
         }
 
@@ -699,13 +732,15 @@
             if (apnContext.getState() == State.IDLE) {
                 ArrayList<ApnSetting> waitingApns = buildWaitingApns(apnContext.getApnType());
                 if (waitingApns.isEmpty()) {
-                    if (DBG) log("No APN found");
+                    if (DBG) log("trySetupData: No APN found");
                     notifyNoData(GsmDataConnection.FailCause.MISSING_UNKNOWN_APN, apnContext);
                     notifyOffApnsOfAvailability(apnContext.getReason(), false);
                     return false;
                 } else {
                     apnContext.setWaitingApns(waitingApns);
-                    log ("Create from mAllApns : " + apnListToString(mAllApns));
+                    if (DBG) {
+                        log ("trySetupData: Create from mAllApns : " + apnListToString(mAllApns));
+                    }
                 }
             }
 
@@ -735,7 +770,7 @@
 
         for (ApnContext apnContext : mApnContexts.values()) {
             if (!apnContext.isReady()) {
-                if (DBG) log("notify disconnected for type:" + apnContext.getApnType());
+                if (DBG) log("notifyOffApnOfAvailability type:" + apnContext.getApnType());
                 mPhone.notifyDataConnection(reason != null ? reason : apnContext.getReason(),
                                             apnContext.getApnType(),
                                             Phone.DataState.DISCONNECTED);
@@ -753,7 +788,7 @@
      * @param reason reason for the clean up.
      */
     protected void cleanUpAllConnections(boolean tearDown, String reason) {
-        if (DBG) log("Clean up all connections due to " + reason);
+        if (DBG) log("cleanUpAllConnections: tearDown=" + tearDown + " reason=" + reason);
 
         for (ApnContext apnContext : mApnContexts.values()) {
             apnContext.setReason(reason);
@@ -784,11 +819,13 @@
     private void cleanUpConnection(boolean tearDown, ApnContext apnContext) {
 
         if (apnContext == null) {
-            if (DBG) log("apn context is null");
+            if (DBG) log("cleanUpConnection: apn context is null");
             return;
         }
 
-        if (DBG) log("Clean up connection due to " + apnContext.getReason());
+        if (DBG) {
+            log("cleanUpConnection: tearDown=" + tearDown + " reason=" + apnContext.getReason());
+        }
 
         // Clear the reconnect alarm, if set.
         if (apnContext.getReconnectIntent() != null) {
@@ -799,24 +836,26 @@
         }
 
         if (apnContext.getState() == State.IDLE || apnContext.getState() == State.DISCONNECTING) {
-            if (DBG) log("state is in " + apnContext.getState());
+            if (DBG) log("cleanUpConnection: state= " + apnContext.getState());
             return;
         }
 
         if (apnContext.getState() == State.FAILED) {
-            if (DBG) log("state is in FAILED");
+            if (DBG) log("cleanUpConnection: state is in FAILED");
             apnContext.setState(State.IDLE);
             return;
         }
 
         DataConnection conn = apnContext.getDataConnection();
         if (conn != null) {
+            DataConnectionAc dcac = mDataConnectionAsyncChannels.get(conn.getDataConnectionId());
             apnContext.setState(State.DISCONNECTING);
             if (tearDown) {
-                Message msg = obtainMessage(EVENT_DISCONNECT_DONE, apnContext);
-                conn.disconnect(apnContext.getReason(), msg);
+                releaseApnContext(apnContext, tearDown);
             } else {
-                conn.resetSynchronously();
+                if (dcac != null) {
+                    dcac.resetSync();
+                }
                 apnContext.setState(State.IDLE);
                 mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
             }
@@ -871,27 +910,31 @@
     }
 
     private GsmDataConnection findFreeDataConnection() {
-        for (DataConnection dc : mDataConnections.values()) {
-            if (dc.isInactive()) {
-                log("found free GsmDataConnection");
-                return (GsmDataConnection) dc;
+        for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+            if (dcac.isInactiveSync()) {
+                log("findFreeDataConnection: found free GsmDataConnection");
+                return (GsmDataConnection) dcac.dataConnection;
             }
         }
-        log("NO free GsmDataConnection");
+        log("findFreeDataConnection: NO free GsmDataConnection");
         return null;
     }
 
     protected GsmDataConnection findReadyDataConnection(ApnSetting apn) {
         if (DBG)
-            log("findReadyDataConnection for apn string <" +
+            log("findReadyDataConnection: apn string <" +
                 (apn!=null?(apn.toString()):"null") +">");
-        for (DataConnection conn : mDataConnections.values()) {
-            GsmDataConnection dc = (GsmDataConnection) conn;
-            if (DBG) log("dc apn string <" +
-                         (dc.getApn() != null ? (dc.getApn().toString()) : "null") + ">");
-            if (dc.getApn() != null && apn != null
-                && dc.getApn().toString().equals(apn.toString())) {
-                return dc;
+        if (apn == null) {
+            return null;
+        }
+        for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+            ApnSetting apnSetting = dcac.getApnSettingSync();
+            if (DBG) {
+                log("findReadyDataConnection: dc apn string <" +
+                         (apnSetting != null ? (apnSetting.toString()) : "null") + ">");
+            }
+            if ((apnSetting != null) && TextUtils.equals(apnSetting.toString(), apn.toString())) {
+                return (GsmDataConnection) dcac.dataConnection;
             }
         }
         return null;
@@ -899,7 +942,7 @@
 
 
     private boolean setupData(ApnContext apnContext) {
-        if (DBG) log("enter setupData!");
+        if (DBG) log("setupData: apnContext=" + apnContext);
         ApnSetting apn;
         GsmDataConnection dc;
 
@@ -920,7 +963,7 @@
         }
 
         if (dc == null) {
-            dc = createDataConnection(apnContext);
+            dc = createDataConnection(apnContext.getApnType());
         }
 
         if (dc == null) {
@@ -928,17 +971,19 @@
             return false;
         }
 
-        apnContext.setApnSetting(apn);
-        apnContext.setDataConnection(dc);
         dc.setProfileId( profileId );
         dc.setActiveApnType(apnContext.getApnType());
+        int refCount = dc.incAndGetRefCount();
+        if (DBG) log("setupData: init dc and apnContext refCount=" + refCount);
+        DataConnectionAc dcac = mDataConnectionAsyncChannels.get(dc.getDataConnectionId());
+        apnContext.setDataConnectionAc(mDataConnectionAsyncChannels.get(dc.getDataConnectionId()));
+        apnContext.setApnSetting(apn);
+        apnContext.setDataConnection(dc);
 
         Message msg = obtainMessage();
         msg.what = EVENT_DATA_SETUP_COMPLETE;
         msg.obj = apnContext;
-
-        if (DBG) log("dc connect!");
-        dc.connect(msg, apn);
+        dc.bringUp(msg, apn);
 
         apnContext.setState(State.INITING);
         mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
@@ -981,7 +1026,7 @@
 
         // TODO: It'd be nice to only do this if the changed entrie(s)
         // match the current operator.
-        if (DBG) log("onApnChanged createAllApnList and cleanUpAllConnections");
+        if (DBG) log("onApnChanged: createAllApnList and cleanUpAllConnections");
         createAllApnList();
         cleanUpAllConnections(isConnected, Phone.REASON_APN_CHANGED);
         if (!isConnected) {
@@ -998,25 +1043,30 @@
     private void onDataStateChanged (AsyncResult ar) {
         ArrayList<DataCallState> dataCallStates;
 
+        if (DBG) log("onDataStateChanged(ar) E");
         dataCallStates = (ArrayList<DataCallState>)(ar.result);
 
         if (ar.exception != null) {
             // This is probably "radio not available" or something
             // of that sort. If so, the whole connection is going
             // to come down soon anyway
+            if (DBG) log("onDataStateChanged(ar): exception; likely radio not available, ignore");
             return;
         }
 
         for (ApnContext apnContext : mApnContexts.values()) {
             onDataStateChanged(dataCallStates, apnContext);
         }
+        if (DBG) log("onDataStateChanged(ar) X");
     }
 
     private void onDataStateChanged (ArrayList<DataCallState> dataCallStates,
                                      ApnContext apnContext) {
+        if (DBG) log("onDataStateChanged(dataCallState, apnContext):  apnContext=" + apnContext);
 
         if (apnContext == null) {
             // Should not happen
+            if (DBG) log("onDataStateChanged(dataCallState, apnContext):  ignore apnContext=null");
             return;
         }
 
@@ -1027,28 +1077,37 @@
             // context is still listed with active = false, which
             // makes it hard to distinguish an activating context from
             // an activated-and-then deactivated one.
-            if (!dataCallStatesHasCID(dataCallStates, apnContext.getDataConnection().getCid())) {
+            DataConnectionAc dcac = apnContext.getDataConnectionAc();
+            if (dcac == null) {
+                if (DBG) log("onDataStateChanged(dataCallState, apnContext):  dcac==null BAD NEWS");
+                return;
+            }
+            int cid = dcac.getCidSync();
+            if (!dataCallStatesHasCID(dataCallStates, cid)) {
                 // It looks like the PDP context has deactivated.
                 // Tear everything down and try to reconnect.
 
-                Log.i(LOG_TAG, "PDP connection has dropped. Reconnecting");
-
+                if (DBG) {
+                    log("onDataStateChanged(dataCallStates,apnContext) " +
+                        "PDP connection has dropped. Reconnecting");
+                }
                 // Add an event log when the network drops PDP
-                int cid = getCellLocationId();
-                EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cid,
+                int cellLocationId = getCellLocationId();
+                EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cellLocationId,
                         TelephonyManager.getDefault().getNetworkType());
 
                 cleanUpConnection(true, apnContext);
-                return;
             } else if (!dataCallStatesHasActiveCID(dataCallStates,
-                    apnContext.getDataConnection().getCid())) {
+                    apnContext.getDataConnectionAc().getCidSync())) {
 
-                Log.i(LOG_TAG, "PDP connection has dropped (active=false case). "
-                                    + " Reconnecting");
+                if (DBG) {
+                    log("onDataStateChanged(dataCallStates,apnContext) " +
+                        "PDP connection has dropped (active=false case). Reconnecting");
+                }
 
                 // Log the network drop on the event log.
-                int cid = getCellLocationId();
-                EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cid,
+                int cellLocationId = getCellLocationId();
+                EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cellLocationId,
                         TelephonyManager.getDefault().getNetworkType());
 
                 cleanUpConnection(true, apnContext);
@@ -1057,9 +1116,10 @@
     }
 
     private void notifyDefaultData(ApnContext apnContext) {
-        if (DBG)
-            log("notifyDefaultData for type: " + apnContext.getApnType()
+        if (DBG) {
+            log("notifyDefaultData: type=" + apnContext.getApnType()
                 + ", reason:" + apnContext.getReason());
+        }
         apnContext.setState(State.CONNECTED);
         // setState(State.CONNECTED);
         mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
@@ -1091,21 +1151,25 @@
             if (mPdpResetCount < maxPdpReset) {
                 mPdpResetCount++;
                 EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET, mSentSinceLastRecv);
+                if (DBG) log("doRecovery() cleanup all connections mPdpResetCount < max");
                 cleanUpAllConnections(true, Phone.REASON_PDP_RESET);
             } else {
                 mPdpResetCount = 0;
                 EventLog.writeEvent(EventLogTags.PDP_REREGISTER_NETWORK, mSentSinceLastRecv);
+                if (DBG) log("doRecovery() re-register getting preferred network type");
                 mPhone.getServiceStateTracker().reRegisterNetwork(null);
             }
             // TODO: Add increasingly drastic recovery steps, eg,
             // reset the radio, reset the device.
+        } else {
+            if (DBG) log("doRecovery(): ignore, we're not connected");
         }
     }
 
     @Override
     protected void startNetStatPoll() {
         if (getOverallState() == State.CONNECTED && mNetStatPollEnabled == false) {
-            log("[DataConnection] Start poll NetStat");
+            if (DBG) log("startNetStatPoll");
             resetPollStats();
             mNetStatPollEnabled = true;
             mPollNetStat.run();
@@ -1116,12 +1180,12 @@
     protected void stopNetStatPoll() {
         mNetStatPollEnabled = false;
         removeCallbacks(mPollNetStat);
-        log("[DataConnection] Stop poll NetStat");
+        if (DBG) log("stopNetStatPoll");
     }
 
     @Override
     protected void restartRadio() {
-        log("************TURN OFF RADIO**************");
+        if (DBG) log("restartRadio: ************TURN OFF RADIO**************");
         cleanUpAllConnections(true, Phone.REASON_RADIO_TURNED_OFF);
         mPhone.getServiceStateTracker().powerOffRadioSafely(this);
         /* Note: no need to call setRadioPower(true).  Assuming the desired
@@ -1202,7 +1266,7 @@
                 if (mNoRecvPollCount < noRecvPollLimit) {
                     // It's possible the PDP context went down and we weren't notified.
                     // Start polling the context list in an attempt to recover.
-                    if (DBG) log("no DATAIN in a while; polling PDP");
+                    if (DBG) log("Polling: no DATAIN in a while; polling PDP");
                     mPhone.mCM.getDataCallList(obtainMessage(EVENT_DATA_STATE_CHANGED));
 
                     mNoRecvPollCount++;
@@ -1212,7 +1276,7 @@
                             Settings.Secure.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS,
                             POLL_NETSTAT_SLOW_MILLIS);
                 } else {
-                    if (DBG) log("Sent " + String.valueOf(mSentSinceLastRecv) +
+                    if (DBG) log("Polling: Sent " + String.valueOf(mSentSinceLastRecv) +
                                         " pkts since last received start recovery process");
                     stopNetStatPoll();
                     sendMessage(obtainMessage(EVENT_START_RECOVERY));
@@ -1262,14 +1326,12 @@
 
     private void reconnectAfterFail(FailCause lastFailCauseCode, ApnContext apnContext) {
         if (apnContext == null) {
-            Log.d(LOG_TAG, "It is impossible");
+            loge("reconnectAfterFail: apnContext == null, impossible");
             return;
         }
         if (apnContext.getState() == State.FAILED) {
             if (!apnContext.getDataConnection().isRetryNeeded()) {
-                if (!apnContext.getApnType().equals(Phone.APN_TYPE_DEFAULT)){
-                    // if no more retries on a secondary APN attempt, tell the world and revert.
-                    apnContext.setDataConnection(null);
+                if (!apnContext.getApnType().equals(Phone.APN_TYPE_DEFAULT)) {
                     notifyDataConnection(Phone.REASON_APN_FAILED);
                     return;
                 }
@@ -1278,7 +1340,7 @@
                     apnContext.getDataConnection().retryForeverUsingLastTimeout();
                 } else {
                     // Try to Re-register to the network.
-                    log("PDP activate failed, Reregistering to the network");
+                    if (DBG) log("reconnectAfterFail: activate failed, Reregistering to network");
                     mReregisterOnReconnectFailure = true;
                     mPhone.getServiceStateTracker().reRegisterNetwork(null);
                     apnContext.getDataConnection().resetRetryCount();
@@ -1287,8 +1349,10 @@
             }
 
             int nextReconnectDelay = apnContext.getDataConnection().getRetryTimer();
-            log("PDP activate failed. Scheduling next attempt for "
+            if (DBG) {
+                log("reconnectAfterFail: activate failed. Scheduling next attempt for "
                     + (nextReconnectDelay / 1000) + "s");
+            }
 
             AlarmManager am =
                 (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
@@ -1305,8 +1369,10 @@
             apnContext.getDataConnection().increaseRetryCount();
 
             if (!shouldPostNotification(lastFailCauseCode)) {
-                Log.d(LOG_TAG, "NOT Posting GPRS Unavailable notification "
+                if (DBG) {
+                    log("reconnectAfterFail: NOT Posting GPRS Unavailable notification "
                                 + "-- likely transient error");
+                }
             } else {
                 notifyNoData(lastFailCauseCode, apnContext);
             }
@@ -1315,7 +1381,7 @@
 
     private void notifyNoData(GsmDataConnection.FailCause lastFailCauseCode,
                               ApnContext apnContext) {
-        if (DBG) log( "notifyNoData for type:" + apnContext.getApnType());
+        if (DBG) log( "notifyNoData: type=" + apnContext.getApnType());
         apnContext.setState(State.FAILED);
         if (lastFailCauseCode.isPermanentFail()
             && (!apnContext.getApnType().equals(Phone.APN_TYPE_DEFAULT))) {
@@ -1327,7 +1393,7 @@
         if (DBG) log("onRecordsLoaded: createAllApnList");
         createAllApnList();
         if (mRadioAvailable) {
-            if (DBG) log("onRecordsLoaded, notifying data availability");
+            if (DBG) log("onRecordsLoaded: notifying data availability");
             notifyDataAvailability(null);
         }
         setupDataOnReadyApns(Phone.REASON_SIM_LOADED);
@@ -1337,7 +1403,8 @@
     protected void onSetDependencyMet(String apnType, boolean met) {
         ApnContext apnContext = mApnContexts.get(apnType);
         if (apnContext == null) {
-            log("ApnContext not found in onSetDependencyMet(" + apnType + ", " + met + ")");
+            loge("onSetDependencyMet: ApnContext not found in onSetDependencyMet(" +
+                    apnType + ", " + met + ")");
             return;
         }
         applyNewState(apnContext, apnContext.isEnabled(), met);
@@ -1373,12 +1440,15 @@
                     }
                     trySetup = true;
                 } else {
-                    // TODO send notifications
-                    if (DBG) {
-                        log("Found existing connection for " + apnContext.getApnType() +
-                                ": " + conn);
-                    }
+                    int refCount = conn.incAndGetRefCount();
                     apnContext.setDataConnection(conn);
+                    apnContext.setDataConnectionAc(
+                            mDataConnectionAsyncChannels.get(conn.getDataConnectionId()));
+                    if (DBG) {
+                        log("applyNewState: Found existing connection for " +
+                                apnContext.getApnType() + " inc refCount=" + refCount +
+                                " conn=" + conn);
+                    }
                 }
             }
         }
@@ -1395,9 +1465,16 @@
             DataConnection conn = c.getDataConnection();
             if (conn != null) {
                 ApnSetting apnSetting = c.getApnSetting();
-                if (apnSetting != null && apnSetting.canHandleType(apnType)) return conn;
+                if (apnSetting != null && apnSetting.canHandleType(apnType)) {
+                    if (DBG) {
+                        log("checkForConnectionForApnContext: apnContext=" + apnContext +
+                                " found conn=" + conn);
+                    }
+                    return conn;
+                }
             }
         }
+        if (DBG) log("checkForConnectionForApnContext: apnContext=" + apnContext + " NO conn");
         return null;
     }
 
@@ -1405,43 +1482,47 @@
     protected void onEnableApn(int apnId, int enabled) {
         ApnContext apnContext = mApnContexts.get(apnIdToType(apnId));
         if (apnContext == null) {
-            log("ApnContext not found in onEnableApn(" + apnId + ", " + enabled + ")");
+            loge("onEnableApn(" + apnId + ", " + enabled + "): NO ApnContext");
             return;
         }
         // TODO change our retry manager to use the appropriate numbers for the new APN
-        log("onEnableApn with ApnContext E");
+        if (DBG) log("onEnableApn: apnContext=" + apnContext + " call applyNewState");
         applyNewState(apnContext, enabled == ENABLED, apnContext.getDependencyMet());
     }
 
     @Override
     // TODO: We shouldnt need this.
     protected boolean onTrySetupData(String reason) {
+        if (DBG) log("onTrySetupData: reason=" + reason);
         setupDataOnReadyApns(reason);
         return true;
     }
 
     protected boolean onTrySetupData(ApnContext apnContext) {
+        if (DBG) log("onTrySetupData: apnContext=" + apnContext);
         return trySetupData(apnContext);
     }
 
     @Override
     protected void onRoamingOff() {
+        if (DBG) log("onRoamingOff");
         setupDataOnReadyApns(Phone.REASON_ROAMING_OFF);
     }
 
     @Override
     protected void onRoamingOn() {
         if (getDataOnRoamingEnabled()) {
+            if (DBG) log("onRoamingOn: setup data on roaming");
             setupDataOnReadyApns(Phone.REASON_ROAMING_ON);
         } else {
-            if (DBG) log("Tear down data connection on roaming.");
+            if (DBG) log("onRoamingOn: Tear down data connection on roaming.");
             cleanUpAllConnections(true, Phone.REASON_ROAMING_ON);
         }
     }
 
     @Override
     protected void onRadioAvailable() {
-
+        if (DBG) log("onRadioAvailable");
         mRadioAvailable = true;
         if (mPhone.getSimulatedRadioControl() != null) {
             // Assume data is connected on the simulator
@@ -1449,7 +1530,7 @@
             // setState(State.CONNECTED);
             notifyDataConnection(null);
 
-            log("We're on the simulator; assuming data is connected");
+            log("onRadioAvailable: We're on the simulator; assuming data is connected");
         }
 
         if (mPhone.mSIMRecords.getRecordsLoaded()) {
@@ -1477,7 +1558,7 @@
             // FIXME  this can be improved
             log("We're on the simulator; assuming radio off is meaningless");
         } else {
-            if (DBG) log("Radio is off and clean up all connection");
+            if (DBG) log("onRadioOffOrNotAvailable: is off and clean up all connections");
             cleanUpAllConnections(false, Phone.REASON_RADIO_TURNED_OFF);
         }
         notifyDataAvailability(null);
@@ -1490,27 +1571,29 @@
 
         if(ar.userObj instanceof ApnContext){
             apnContext = (ApnContext)ar.userObj;
+        } else {
+            throw new RuntimeException("onDataSetupComplete: No apnContext");
         }
+        DataConnectionAc dcac = apnContext.getDataConnectionAc();
+        if (dcac == null) {
+            throw new RuntimeException("onDataSetupCompete: No dcac");
+        }
+        DataConnection dc = apnContext.getDataConnection();
 
         if (ar.exception == null) {
-            // Everything is setup
-            // TODO: We should clear LinkProperties/Capabilities when torn down or disconnected
             if (DBG) {
                 log(String.format("onDataSetupComplete: success apn=%s",
-                    apnContext.getWaitingApns().get(0).apn));
+                    apnContext.getWaitingApns().get(0).apn) + " refCount=" + dc.getRefCount());
             }
-            mLinkProperties = getLinkProperties(apnContext.getApnType());
-            mLinkCapabilities = getLinkCapabilities(apnContext.getApnType());
-
             ApnSetting apn = apnContext.getApnSetting();
             if (apn.proxy != null && apn.proxy.length() != 0) {
                 try {
                     ProxyProperties proxy = new ProxyProperties(apn.proxy,
                             Integer.parseInt(apn.port), null);
-                    mLinkProperties.setHttpProxy(proxy);
+                    dcac.setLinkPropertiesHttpProxySync(proxy);
                 } catch (NumberFormatException e) {
-                    loge("NumberFormatException making ProxyProperties (" + apn.port +
-                            "): " + e);
+                    loge("onDataSetupComplete: NumberFormatException making ProxyProperties (" +
+                            apn.port + "): " + e);
                 }
             }
 
@@ -1518,7 +1601,7 @@
             if(TextUtils.equals(apnContext.getApnType(),Phone.APN_TYPE_DEFAULT)) {
                 SystemProperties.set("gsm.defaultpdpcontext.active", "true");
                 if (canSetPreferApn && mPreferredApn == null) {
-                    log("PREFERED APN is null");
+                    if (DBG) log("onDataSetupComplete: PREFERED APN is null");
                     mPreferredApn = apnContext.getApnSetting();
                     if (mPreferredApn != null) {
                         setPreferredApn(mPreferredApn.id);
@@ -1528,15 +1611,13 @@
                 SystemProperties.set("gsm.defaultpdpcontext.active", "false");
             }
             notifyDefaultData(apnContext);
-
-            // TODO: For simultaneous PDP support, we need to build another
-            // trigger another TRY_SETUP_DATA for the next APN type.  (Note
-            // that the existing connection may service that type, in which
-            // case we should try the next type, etc.
-            // I dont believe for simultaneous PDP you need to trigger. Each
-            // Connection should be independent and they can be setup simultaneously
-            // So, dont have to wait till one is finished.
         } else {
+            int refCount = releaseApnContext(apnContext, false);
+            if (DBG) {
+                log(String.format("onDataSetupComplete: error apn=%s",
+                    apnContext.getWaitingApns().get(0).apn) + " refCount=" + refCount);
+            }
+
             GsmDataConnection.FailCause cause;
             cause = (GsmDataConnection.FailCause) (ar.result);
             if (DBG) {
@@ -1573,7 +1654,6 @@
                         log("onDataSetupComplete: All APN's had permanent failures, stop retrying");
                     }
                     apnContext.setState(State.FAILED);
-                    apnContext.setDataConnection(null);
                     notifyDataConnection(Phone.REASON_APN_FAILED);
                 } else {
                     if (DBG) log("onDataSetupComplete: Not all permanent failures, retry");
@@ -1597,7 +1677,7 @@
     protected void onDisconnectDone(int connId, AsyncResult ar) {
         ApnContext apnContext = null;
 
-        if(DBG) log("EVENT_DISCONNECT_DONE connId=" + connId);
+        if(DBG) log("onDisconnectDone: EVENT_DISCONNECT_DONE connId=" + connId);
         if (ar.userObj instanceof ApnContext) {
             apnContext = (ApnContext) ar.userObj;
         } else {
@@ -1610,9 +1690,7 @@
 
         // Check if APN disabled.
         if (apnContext.getPendingAction() == ApnContext.PENDING_ACTION_APN_DISABLE) {
-           apnContext.setEnabled(false);
            apnContext.setPendingAction(ApnContext.PENDING_ACTION_NONE);
-           apnContext.setDataConnection(null);
         }
         mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
 
@@ -1648,7 +1726,9 @@
 
     @Override
     protected void onVoiceCallStarted() {
+        if (DBG) log("onVoiceCallStarted");
         if (isConnected() && ! mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
+            if (DBG) log("onVoiceCallStarted stop polling");
             stopNetStatPoll();
             notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
         }
@@ -1656,6 +1736,7 @@
 
     @Override
     protected void onVoiceCallEnded() {
+        if (DBG) log("onVoiceCallEnded");
         if (isConnected()) {
             if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
                 startNetStatPoll();
@@ -1688,10 +1769,10 @@
 
     @Override
     protected void notifyDataConnection(String reason) {
-        if (DBG) log("notify all enabled connection for:" + reason);
+        if (DBG) log("notifyDataConnection: reason=" + reason);
         for (ApnContext apnContext : mApnContexts.values()) {
             if (apnContext.isReady()) {
-                if (DBG) log("notify for type:"+apnContext.getApnType());
+                if (DBG) log("notifyDataConnection: type:"+apnContext.getApnType());
                 mPhone.notifyDataConnection(reason != null ? reason : apnContext.getReason(),
                         apnContext.getApnType());
             }
@@ -1738,17 +1819,16 @@
     }
 
     /** Return the id for a new data connection */
-    private GsmDataConnection createDataConnection(ApnContext apnContext) {
-        String apnType = apnContext.getApnType();
-        log("createDataConnection(" + apnType + ") E");
+    private GsmDataConnection createDataConnection(String apnType) {
+        if (DBG) log("createDataConnection(" + apnType + ") E");
         RetryManager rm = new RetryManager();
 
         if (apnType.equals(Phone.APN_TYPE_DEFAULT)) {
             if (!rm.configure(SystemProperties.get("ro.gsm.data_retry_config"))) {
                 if (!rm.configure(DEFAULT_DATA_RETRY_CONFIG)) {
                     // Should never happen, log an error and default to a simple linear sequence.
-                    log("Could not configure using DEFAULT_DATA_RETRY_CONFIG="
-                            + DEFAULT_DATA_RETRY_CONFIG);
+                    loge("createDataConnection: Could not configure using " +
+                            "DEFAULT_DATA_RETRY_CONFIG=" + DEFAULT_DATA_RETRY_CONFIG);
                     rm.configure(20, 2000, 1000);
                 }
             }
@@ -1756,8 +1836,8 @@
             if (!rm.configure(SystemProperties.get("ro.gsm.2nd_data_retry_config"))) {
                 if (!rm.configure(SECONDARY_DATA_RETRY_CONFIG)) {
                     // Should never happen, log an error and default to a simple sequence.
-                    log("Could note configure using SECONDARY_DATA_RETRY_CONFIG="
-                            + SECONDARY_DATA_RETRY_CONFIG);
+                    loge("createDataConnection: Could note configure using " +
+                            "SECONDARY_DATA_RETRY_CONFIG=" + SECONDARY_DATA_RETRY_CONFIG);
                     rm.configure("max_retries=3, 333, 333, 333");
                 }
             }
@@ -1767,18 +1847,25 @@
         GsmDataConnection conn = GsmDataConnection.makeDataConnection(mPhone, id, rm);
         conn.resetRetryCount();
         mDataConnections.put(id, conn);
-        apnContext.setDataConnection(conn);
+        DataConnectionAc dcac = new DataConnectionAc(conn, LOG_TAG);
+        int status = dcac.fullyConnectSync(mPhone.getContext(), this, conn.getHandler());
+        if (status == AsyncChannel.STATUS_SUCCESSFUL) {
+            mDataConnectionAsyncChannels.put(dcac.dataConnection.getDataConnectionId(), dcac);
+        } else {
+            loge("createDataConnection: Could not connect to dcac.mDc=" + dcac.dataConnection +
+                    " status=" + status);
+        }
 
-        log("createDataConnection(" + apnType + ") X id=" + id);
+        if (DBG) log("createDataConnection(" + apnType + ") X id=" + id);
         return conn;
     }
 
     private void destroyDataConnections() {
         if(mDataConnections != null) {
-            log("destroyDataConnectionList clear mDataConnectionList");
+            if (DBG) log("destroyDataConnections: clear mDataConnectionList");
             mDataConnections.clear();
         } else {
-            log("destroyDataConnectionList mDataConnecitonList is empty, ignore");
+            if (DBG) log("destroyDataConnectionList mDataConnecitonList is empty, ignore");
         }
     }
 
@@ -1802,8 +1889,10 @@
         String operator = mPhone.mSIMRecords.getSIMOperatorNumeric();
         if (requestedApnType.equals(Phone.APN_TYPE_DEFAULT)) {
             if (canSetPreferApn && mPreferredApn != null) {
-                log("Preferred APN:" + operator + ":"
+                if (DBG) {
+                    log("buildWaitingApns: Preferred APN:" + operator + ":"
                         + mPreferredApn.numeric + ":" + mPreferredApn);
+                }
                 if (mPreferredApn.numeric.equals(operator)) {
                     apnList.add(mPreferredApn);
                     if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
@@ -1894,10 +1983,10 @@
 
     @Override
     public void handleMessage (Message msg) {
-        if (DBG) log("GSMDataConnTrack handleMessage "+msg);
+        if (DBG) log("handleMessage msg=" + msg);
 
         if (!mPhone.mIsTheCurrentActivePhone || mIsDisposed) {
-            log("Ignore GSM msgs since GSM phone is inactive");
+            loge("handleMessage: Ignore GSM msgs since GSM phone is inactive");
             return;
         }
 
@@ -1941,7 +2030,7 @@
                  * PDP context and notify us with PDP_CONTEXT_CHANGED.
                  * But we should stop the network polling and prevent reset PDP.
                  */
-                log("EVENT_PS_RESTRICT_ENABLED " + mIsPsRestricted);
+                if (DBG) log("EVENT_PS_RESTRICT_ENABLED " + mIsPsRestricted);
                 stopNetStatPoll();
                 mIsPsRestricted = true;
                 break;
@@ -1951,7 +2040,7 @@
                  * When PS restrict is removed, we need setup PDP connection if
                  * PDP connection is down.
                  */
-                log("EVENT_PS_RESTRICT_DISABLED " + mIsPsRestricted);
+                if (DBG) log("EVENT_PS_RESTRICT_DISABLED " + mIsPsRestricted);
                 mIsPsRestricted  = false;
                 if (isConnected()) {
                     startNetStatPoll();
@@ -1968,19 +2057,20 @@
             case EVENT_TRY_SETUP_DATA:
                 if (msg.obj instanceof ApnContext) {
                     onTrySetupData((ApnContext)msg.obj);
+                } else if (msg.obj instanceof String) {
+                    onTrySetupData((String)msg.obj);
                 } else {
-                    if (msg.obj instanceof String) {
-                        onTrySetupData((String)msg.obj);
-                    }
+                    loge("EVENT_TRY_SETUP request w/o apnContext or String");
                 }
                 break;
 
             case EVENT_CLEAN_UP_CONNECTION:
                 boolean tearDown = (msg.arg1 == 0) ? false : true;
+                if (DBG) log("EVENT_CLEAN_UP_CONNECTION tearDown=" + tearDown);
                 if (msg.obj instanceof ApnContext) {
                     cleanUpConnection(tearDown, (ApnContext)msg.obj);
                 } else {
-                    loge("[GsmDataConnectionTracker] connectpion cleanup request w/o apn context");
+                    loge("EVENT_CLEAN_UP_CONNECTION request w/o apn context");
                 }
                 break;
             default:
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 6455d84..7f9fc31 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -23,6 +23,7 @@
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
 import android.net.ProxyProperties;
+import android.net.RouteInfo;
 import android.net.wifi.WifiConfiguration.IpAssignment;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiConfiguration.ProxySettings;
@@ -120,7 +121,7 @@
     private static final String ipConfigFile = Environment.getDataDirectory() +
             "/misc/wifi/ipconfig.txt";
 
-    private static final int IPCONFIG_FILE_VERSION = 1;
+    private static final int IPCONFIG_FILE_VERSION = 2;
 
     /* IP and proxy configuration keys */
     private static final String ID_KEY = "id";
@@ -445,9 +446,8 @@
             if (iter.hasNext()) {
                 LinkAddress linkAddress = iter.next();
                 dhcpInfoInternal.ipAddress = linkAddress.getAddress().getHostAddress();
-                Iterator<InetAddress>gateways = linkProperties.getGateways().iterator();
-                if (gateways.hasNext()) {
-                    dhcpInfoInternal.gateway = gateways.next().getHostAddress();
+                for (RouteInfo route : linkProperties.getRoutes()) {
+                    dhcpInfoInternal.addRoute(route);
                 }
                 dhcpInfoInternal.prefixLength = linkAddress.getNetworkPrefixLength();
                 Iterator<InetAddress> dnsIterator = linkProperties.getDnses().iterator();
@@ -604,9 +604,22 @@
                                     out.writeUTF(linkAddr.getAddress().getHostAddress());
                                     out.writeInt(linkAddr.getNetworkPrefixLength());
                                 }
-                                for (InetAddress gateway : linkProperties.getGateways()) {
+                                for (RouteInfo route : linkProperties.getRoutes()) {
                                     out.writeUTF(GATEWAY_KEY);
-                                    out.writeUTF(gateway.getHostAddress());
+                                    LinkAddress dest = route.getDestination();
+                                    if (dest != null) {
+                                        out.writeInt(1);
+                                        out.writeUTF(dest.getAddress().getHostAddress());
+                                        out.writeInt(dest.getNetworkPrefixLength());
+                                    } else {
+                                        out.writeInt(0);
+                                    }
+                                    if (route.getGateway() != null) {
+                                        out.writeInt(1);
+                                        out.writeUTF(route.getGateway().getHostAddress());
+                                    } else {
+                                        out.writeInt(0);
+                                    }
                                 }
                                 for (InetAddress inetAddr : linkProperties.getDnses()) {
                                     out.writeUTF(DNS_KEY);
@@ -682,7 +695,8 @@
             in = new DataInputStream(new BufferedInputStream(new FileInputStream(
                     ipConfigFile)));
 
-            if (in.readInt() != IPCONFIG_FILE_VERSION) {
+            int version = in.readInt();
+            if (version != 2 && version != 1) {
                 Log.e(TAG, "Bad version on IP configuration file, ignore read");
                 return;
             }
@@ -709,8 +723,22 @@
                                     NetworkUtils.numericToInetAddress(in.readUTF()), in.readInt());
                             linkProperties.addLinkAddress(linkAddr);
                         } else if (key.equals(GATEWAY_KEY)) {
-                            linkProperties.addGateway(
-                                    NetworkUtils.numericToInetAddress(in.readUTF()));
+                            LinkAddress dest = null;
+                            InetAddress gateway = null;
+                            if (version == 1) {
+                                // only supported default gateways - leave the dest/prefix empty
+                                gateway = NetworkUtils.numericToInetAddress(in.readUTF());
+                            } else {
+                                if (in.readInt() == 1) {
+                                    dest = new LinkAddress(
+                                            NetworkUtils.numericToInetAddress(in.readUTF()),
+                                            in.readInt());
+                                }
+                                if (in.readInt() == 1) {
+                                    gateway = NetworkUtils.numericToInetAddress(in.readUTF());
+                                }
+                            }
+                            linkProperties.addRoute(new RouteInfo(dest, gateway));
                         } else if (key.equals(DNS_KEY)) {
                             linkProperties.addDns(
                                     NetworkUtils.numericToInetAddress(in.readUTF()));
@@ -1022,22 +1050,21 @@
                         .getLinkAddresses();
                 Collection<InetAddress> currentDnses = currentConfig.linkProperties.getDnses();
                 Collection<InetAddress> newDnses = newConfig.linkProperties.getDnses();
-                Collection<InetAddress> currentGateways =
-                        currentConfig.linkProperties.getGateways();
-                Collection<InetAddress> newGateways = newConfig.linkProperties.getGateways();
+                Collection<RouteInfo> currentRoutes = currentConfig.linkProperties.getRoutes();
+                Collection<RouteInfo> newRoutes = newConfig.linkProperties.getRoutes();
 
                 boolean linkAddressesDiffer =
                         (currentLinkAddresses.size() != newLinkAddresses.size()) ||
                         !currentLinkAddresses.containsAll(newLinkAddresses);
                 boolean dnsesDiffer = (currentDnses.size() != newDnses.size()) ||
                         !currentDnses.containsAll(newDnses);
-                boolean gatewaysDiffer = (currentGateways.size() != newGateways.size()) ||
-                        !currentGateways.containsAll(newGateways);
+                boolean routesDiffer = (currentRoutes.size() != newRoutes.size()) ||
+                        !currentRoutes.containsAll(newRoutes);
 
                 if ((currentConfig.ipAssignment != newConfig.ipAssignment) ||
                         linkAddressesDiffer ||
                         dnsesDiffer ||
-                        gatewaysDiffer) {
+                        routesDiffer) {
                     ipChanged = true;
                 }
                 break;
@@ -1112,8 +1139,8 @@
         for (LinkAddress linkAddr : config.linkProperties.getLinkAddresses()) {
             linkProperties.addLinkAddress(linkAddr);
         }
-        for (InetAddress gateway : config.linkProperties.getGateways()) {
-            linkProperties.addGateway(gateway);
+        for (RouteInfo route : config.linkProperties.getRoutes()) {
+            linkProperties.addRoute(route);
         }
         for (InetAddress dns : config.linkProperties.getDnses()) {
             linkProperties.addDns(dns);
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index e1ded03..a6b1a2c 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -48,6 +48,7 @@
 import android.net.ConnectivityManager;
 import android.net.DhcpInfo;
 import android.net.DhcpInfoInternal;
+import android.net.DhcpStateMachine;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -152,6 +153,7 @@
     private NetworkInfo mNetworkInfo;
     private SupplicantStateTracker mSupplicantStateTracker;
     private WpsStateMachine mWpsStateMachine;
+    private DhcpStateMachine mDhcpStateMachine;
 
     private AlarmManager mAlarmManager;
     private PendingIntent mScanIntent;
@@ -189,10 +191,10 @@
     static final int CMD_START_DRIVER                     = BASE + 13;
     /* Start the driver */
     static final int CMD_STOP_DRIVER                      = BASE + 14;
-    /* Indicates DHCP succeded */
-    static final int CMD_IP_CONFIG_SUCCESS                = BASE + 15;
-    /* Indicates DHCP failed */
-    static final int CMD_IP_CONFIG_FAILURE                = BASE + 16;
+    /* Indicates Static IP succeded */
+    static final int CMD_STATIC_IP_SUCCESS                = BASE + 15;
+    /* Indicates Static IP failed */
+    static final int CMD_STATIC_IP_FAILURE                = BASE + 16;
 
     /* Start the soft access point */
     static final int CMD_START_AP                         = BASE + 21;
@@ -338,8 +340,11 @@
      */
     private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
 
-    private static final int POWER_MODE_ACTIVE = 1;
-    private static final int POWER_MODE_AUTO = 0;
+    static final int POWER_MODE_ACTIVE = 1;
+    static final int POWER_MODE_AUTO = 0;
+
+    /* Tracks the power mode for restoration after a DHCP request/renewal goes through */
+    private int mPowerMode = POWER_MODE_AUTO;
 
     /**
      * See {@link Settings.Secure#WIFI_SCAN_INTERVAL_MS}. This is the default value if a
@@ -1392,8 +1397,10 @@
          */
         NetworkUtils.resetConnections(mInterfaceName);
 
-        if (!NetworkUtils.stopDhcp(mInterfaceName)) {
-            Log.e(TAG, "Could not stop DHCP");
+        if (mDhcpStateMachine != null) {
+            mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
+            mDhcpStateMachine.quit();
+            mDhcpStateMachine = null;
         }
 
         /* Disable interface */
@@ -1419,6 +1426,100 @@
 
     }
 
+    void handlePreDhcpSetup() {
+        if (!mBluetoothConnectionActive) {
+            /*
+             * There are problems setting the Wi-Fi driver's power
+             * mode to active when bluetooth coexistence mode is
+             * enabled or sense.
+             * <p>
+             * We set Wi-Fi to active mode when
+             * obtaining an IP address because we've found
+             * compatibility issues with some routers with low power
+             * mode.
+             * <p>
+             * In order for this active power mode to properly be set,
+             * we disable coexistence mode until we're done with
+             * obtaining an IP address.  One exception is if we
+             * are currently connected to a headset, since disabling
+             * coexistence would interrupt that connection.
+             */
+            // Disable the coexistence mode
+            WifiNative.setBluetoothCoexistenceModeCommand(
+                    WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
+        }
+
+        mPowerMode =  WifiNative.getPowerModeCommand();
+        if (mPowerMode < 0) {
+            // Handle the case where supplicant driver does not support
+            // getPowerModeCommand.
+            mPowerMode = WifiStateMachine.POWER_MODE_AUTO;
+        }
+        if (mPowerMode != WifiStateMachine.POWER_MODE_ACTIVE) {
+            WifiNative.setPowerModeCommand(WifiStateMachine.POWER_MODE_ACTIVE);
+        }
+    }
+
+
+    void handlePostDhcpSetup() {
+        /* restore power mode */
+        WifiNative.setPowerModeCommand(mPowerMode);
+
+        // Set the coexistence mode back to its default value
+        WifiNative.setBluetoothCoexistenceModeCommand(
+                WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
+    }
+
+    private void handleSuccessfulIpConfiguration(DhcpInfoInternal dhcpInfoInternal) {
+        synchronized (mDhcpInfoInternal) {
+            mDhcpInfoInternal = dhcpInfoInternal;
+        }
+        mLastSignalLevel = -1; // force update of signal strength
+        WifiConfigStore.setIpConfiguration(mLastNetworkId, dhcpInfoInternal);
+        InetAddress addr = NetworkUtils.numericToInetAddress(dhcpInfoInternal.ipAddress);
+        mWifiInfo.setInetAddress(addr);
+        if (getNetworkDetailedState() == DetailedState.CONNECTED) {
+            //DHCP renewal in connected state
+            LinkProperties linkProperties = dhcpInfoInternal.makeLinkProperties();
+            linkProperties.setHttpProxy(WifiConfigStore.getProxyProperties(mLastNetworkId));
+            linkProperties.setInterfaceName(mInterfaceName);
+            if (!linkProperties.equals(mLinkProperties)) {
+                Log.d(TAG, "Link configuration changed for netId: " + mLastNetworkId
+                    + " old: " + mLinkProperties + "new: " + linkProperties);
+                NetworkUtils.resetConnections(mInterfaceName);
+                mLinkProperties = linkProperties;
+                sendLinkConfigurationChangedBroadcast();
+            }
+        } else {
+            configureLinkProperties();
+            setNetworkDetailedState(DetailedState.CONNECTED);
+            sendNetworkStateChangeBroadcast(mLastBssid);
+        }
+    }
+
+    private void handleFailedIpConfiguration() {
+        Log.e(TAG, "IP configuration failed");
+
+        mWifiInfo.setInetAddress(null);
+        /**
+         * If we've exceeded the maximum number of retries for DHCP
+         * to a given network, disable the network
+         */
+        if (++mReconnectCount > getMaxDhcpRetries()) {
+            Log.e(TAG, "Failed " +
+                    mReconnectCount + " times, Disabling " + mLastNetworkId);
+            WifiConfigStore.disableNetwork(mLastNetworkId);
+            mReconnectCount = 0;
+        }
+
+        /* DHCP times out after about 30 seconds, we do a
+         * disconnect and an immediate reconnect to try again
+         */
+        WifiNative.disconnectCommand();
+        WifiNative.reconnectCommand();
+
+    }
+
 
     /*********************************************************
      * Notifications from WifiMonitor
@@ -1590,6 +1691,8 @@
                 case CMD_FORGET_NETWORK:
                 case CMD_RSSI_POLL:
                 case CMD_ENABLE_ALL_NETWORKS:
+                case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+                case DhcpStateMachine.CMD_POST_DHCP_ACTION:
                     break;
                 case CMD_START_WPS:
                     /* Return failure when the state machine cannot handle WPS initiation*/
@@ -2462,74 +2565,18 @@
     }
 
     class ConnectingState extends State {
-        boolean mModifiedBluetoothCoexistenceMode;
-        int mPowerMode;
-        boolean mUseStaticIp;
-        Thread mDhcpThread;
 
         @Override
         public void enter() {
             if (DBG) Log.d(TAG, getName() + "\n");
             EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
-            mUseStaticIp = WifiConfigStore.isUsingStaticIp(mLastNetworkId);
-            if (!mUseStaticIp) {
-                mDhcpThread = null;
-                mModifiedBluetoothCoexistenceMode = false;
-                mPowerMode = POWER_MODE_AUTO;
 
-                if (!mBluetoothConnectionActive) {
-                    /*
-                     * There are problems setting the Wi-Fi driver's power
-                     * mode to active when bluetooth coexistence mode is
-                     * enabled or sense.
-                     * <p>
-                     * We set Wi-Fi to active mode when
-                     * obtaining an IP address because we've found
-                     * compatibility issues with some routers with low power
-                     * mode.
-                     * <p>
-                     * In order for this active power mode to properly be set,
-                     * we disable coexistence mode until we're done with
-                     * obtaining an IP address.  One exception is if we
-                     * are currently connected to a headset, since disabling
-                     * coexistence would interrupt that connection.
-                     */
-                    mModifiedBluetoothCoexistenceMode = true;
-
-                    // Disable the coexistence mode
-                    WifiNative.setBluetoothCoexistenceModeCommand(
-                            WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
-                }
-
-                mPowerMode =  WifiNative.getPowerModeCommand();
-                if (mPowerMode < 0) {
-                  // Handle the case where supplicant driver does not support
-                  // getPowerModeCommand.
-                    mPowerMode = POWER_MODE_AUTO;
-                }
-                if (mPowerMode != POWER_MODE_ACTIVE) {
-                    WifiNative.setPowerModeCommand(POWER_MODE_ACTIVE);
-                }
-
-                Log.d(TAG, "DHCP request started");
-                mDhcpThread = new Thread(new Runnable() {
-                    public void run() {
-                        DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
-                        if (NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal)) {
-                            Log.d(TAG, "DHCP request succeeded");
-                            synchronized (mDhcpInfoInternal) {
-                                mDhcpInfoInternal = dhcpInfoInternal;
-                            }
-                            WifiConfigStore.setIpConfiguration(mLastNetworkId, dhcpInfoInternal);
-                            sendMessage(CMD_IP_CONFIG_SUCCESS);
-                        } else {
-                            Log.d(TAG, "DHCP request failed: " +
-                                    NetworkUtils.getDhcpError());
-                            sendMessage(CMD_IP_CONFIG_FAILURE);
-                        }
-                    }
-                });
-                mDhcpThread.start();
+             if (!WifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
+                //start DHCP
+                mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
+                        mContext, WifiStateMachine.this, mInterfaceName);
+                mDhcpStateMachine.registerForPreDhcpNotification();
+                mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
             } else {
                 DhcpInfoInternal dhcpInfoInternal = WifiConfigStore.getIpConfiguration(
                         mLastNetworkId);
@@ -2541,16 +2588,13 @@
                 try {
                     netd.setInterfaceConfig(mInterfaceName, ifcg);
                     Log.v(TAG, "Static IP configuration succeeded");
-                    synchronized (mDhcpInfoInternal) {
-                        mDhcpInfoInternal = dhcpInfoInternal;
-                    }
-                    sendMessage(CMD_IP_CONFIG_SUCCESS);
+                    sendMessage(CMD_STATIC_IP_SUCCESS, dhcpInfoInternal);
                 } catch (RemoteException re) {
                     Log.v(TAG, "Static IP configuration failed: " + re);
-                    sendMessage(CMD_IP_CONFIG_FAILURE);
+                    sendMessage(CMD_STATIC_IP_FAILURE);
                 } catch (IllegalStateException e) {
                     Log.v(TAG, "Static IP configuration failed: " + e);
-                    sendMessage(CMD_IP_CONFIG_FAILURE);
+                    sendMessage(CMD_STATIC_IP_FAILURE);
                 }
             }
          }
@@ -2559,44 +2603,26 @@
           if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
 
           switch(message.what) {
-              case CMD_IP_CONFIG_SUCCESS:
-                  mLastSignalLevel = -1; // force update of signal strength
-                  InetAddress addr;
-                  synchronized (mDhcpInfoInternal) {
-                      addr = NetworkUtils.numericToInetAddress(mDhcpInfoInternal.ipAddress);
+              case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+                  handlePreDhcpSetup();
+                  mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE);
+                  break;
+              case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+                  handlePostDhcpSetup();
+                  if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
+                      handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj);
+                      transitionTo(mConnectedState);
+                  } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
+                      handleFailedIpConfiguration();
+                      transitionTo(mDisconnectingState);
                   }
-                  mWifiInfo.setInetAddress(addr);
-                  configureLinkProperties();
-                  if (getNetworkDetailedState() == DetailedState.CONNECTED) {
-                      sendLinkConfigurationChangedBroadcast();
-                  } else {
-                      setNetworkDetailedState(DetailedState.CONNECTED);
-                      sendNetworkStateChangeBroadcast(mLastBssid);
-                  }
-                  //TODO: The framework is not detecting a DHCP renewal and a possible
-                  //IP change. we should detect this and send out a config change broadcast
+                  break;
+              case CMD_STATIC_IP_SUCCESS:
+                  handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj);
                   transitionTo(mConnectedState);
                   break;
-              case CMD_IP_CONFIG_FAILURE:
-                  mWifiInfo.setInetAddress(null);
-
-                  Log.e(TAG, "IP configuration failed");
-                  /**
-                   * If we've exceeded the maximum number of retries for DHCP
-                   * to a given network, disable the network
-                   */
-                  if (++mReconnectCount > getMaxDhcpRetries()) {
-                      Log.e(TAG, "Failed " +
-                              mReconnectCount + " times, Disabling " + mLastNetworkId);
-                      WifiConfigStore.disableNetwork(mLastNetworkId);
-                      mReconnectCount = 0;
-                  }
-
-                  /* DHCP times out after about 30 seconds, we do a
-                   * disconnect and an immediate reconnect to try again
-                   */
-                  WifiNative.disconnectCommand();
-                  WifiNative.reconnectCommand();
+              case CMD_STATIC_IP_FAILURE:
+                  handleFailedIpConfiguration();
                   transitionTo(mDisconnectingState);
                   break;
               case CMD_DISCONNECT:
@@ -2640,23 +2666,6 @@
           EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
           return HANDLED;
       }
-
-      @Override
-      public void exit() {
-          /* reset power state & bluetooth coexistence if on DHCP */
-          if (!mUseStaticIp) {
-              if (mPowerMode != POWER_MODE_ACTIVE) {
-                  WifiNative.setPowerModeCommand(mPowerMode);
-              }
-
-              if (mModifiedBluetoothCoexistenceMode) {
-                  // Set the coexistence mode back to its default value
-                  WifiNative.setBluetoothCoexistenceModeCommand(
-                          WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
-              }
-          }
-
-      }
     }
 
     class ConnectedState extends State {
@@ -2674,6 +2683,19 @@
             if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
             boolean eventLoggingEnabled = true;
             switch (message.what) {
+              case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+                  handlePreDhcpSetup();
+                  mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE);
+                  break;
+              case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+                  handlePostDhcpSetup();
+                  if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
+                      handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj);
+                  } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
+                      handleFailedIpConfiguration();
+                      transitionTo(mDisconnectingState);
+                  }
+                  break;
                 case CMD_DISCONNECT:
                     WifiNative.disconnectCommand();
                     transitionTo(mDisconnectingState);