Merge "Continuation of the unified account chooser flow." into ics-factoryrom
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index eef658e..7046008 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -83,6 +83,12 @@
 
     String[] getTetheredIfaces();
 
+    /**
+     * Return list of interface pairs that are actively tethered.  Even indexes are
+     * remote interface, and odd indexes are corresponding local interfaces.
+     */
+    String[] getTetheredIfacePairs();
+
     String[] getTetheringErroredIfaces();
 
     String[] getTetherableUsbRegexs();
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index d8ac31f..a5cdf70 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -441,10 +441,10 @@
             final long curStart = bucketStart[i];
             final long curEnd = curStart + bucketDuration;
 
-            // bucket is older than record; we're finished
-            if (curEnd < start) break;
-            // bucket is newer than record; keep looking
-            if (curStart > end) continue;
+            // bucket is older than request; we're finished
+            if (curEnd <= start) break;
+            // bucket is newer than request; keep looking
+            if (curStart >= end) continue;
 
             // include full value for active buckets, otherwise only fractional
             final boolean activeBucket = curStart < now && curEnd > now;
@@ -466,7 +466,6 @@
             if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
             if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
         }
-
         return entry;
     }
 
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 47cfa73..18eb9f6 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -53,25 +53,33 @@
     public static final int UID_REMOVED = -4;
 
     /**
+     * Special UID value used when collecting {@link NetworkStatsHistory} for
+     * tethering traffic.
+     *
+     * @hide
+     */
+    public static final int UID_TETHERING = -5;
+
+    /**
      * Default tag value for {@link DownloadManager} traffic.
      *
      * @hide
      */
-    public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFF0001;
+    public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFFFF01;
 
     /**
      * Default tag value for {@link MediaPlayer} traffic.
      *
      * @hide
      */
-    public static final int TAG_SYSTEM_MEDIA = 0xFFFF0002;
+    public static final int TAG_SYSTEM_MEDIA = 0xFFFFFF02;
 
     /**
      * Default tag value for {@link BackupManager} traffic.
      *
      * @hide
      */
-    public static final int TAG_SYSTEM_BACKUP = 0xFFFF0003;
+    public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03;
 
     /**
      * Snapshot of {@link NetworkStats} when the currently active profiling
@@ -90,6 +98,10 @@
      * <p>
      * Changes only take effect during subsequent calls to
      * {@link #tagSocket(Socket)}.
+     * <p>
+     * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
+     * used internally by system services like {@link DownloadManager} when
+     * performing traffic on behalf of an application.
      */
     public static void setThreadStatsTag(int tag) {
         NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 4e5645d..66373fe 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -231,6 +231,13 @@
     NetworkStats getNetworkStatsUidDetail(int uid);
 
     /**
+     * Return summary of network statistics for the requested pairs of
+     * tethering interfaces.  Even indexes are remote interface, and odd
+     * indexes are corresponding local interfaces.
+     */
+    NetworkStats getNetworkStatsTethering(in String[] ifacePairs);
+
+    /**
      * Set quota for an interface.
      */
     void setInterfaceQuota(String iface, long quotaBytes);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e5882fc..bc5994e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4031,8 +4031,6 @@
         public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history";
         /** {@hide} */
         public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history";
-        /** {@hide} */
-        public static final String NETSTATS_FORCE_COMPLETE_POLL = "netstats_force_complete_poll";
 
         /** Preferred NTP server. {@hide} */
         public static final String NTP_SERVER = "ntp_server";
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index cfe4cb7..ba89ef3 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3654,7 +3654,8 @@
                 vt.computeCurrentVelocity(1000, mMaximumVelocity);
                 final float yvel = -vt.getYVelocity(activeId);
 
-                if (scroller.isScrollingInDirection(0, yvel)) {
+                if (Math.abs(yvel) >= mMinimumVelocity
+                        && scroller.isScrollingInDirection(0, yvel)) {
                     // Keep the fling alive a little longer
                     postDelayed(this, FLYWHEEL_TIMEOUT);
                 } else {
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 542a1ef..e571998 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -532,7 +532,7 @@
         final int dx = mScrollerX.mFinal - mScrollerX.mStart;
         final int dy = mScrollerY.mFinal - mScrollerY.mStart;
         return !isFinished() && Math.signum(xvel) == Math.signum(dx) &&
-        Math.signum(yvel) == Math.signum(dy);
+                Math.signum(yvel) == Math.signum(dy);
     }
 
     static class SplineOverScroller {
diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java
index 29ad15b..f9c2cce 100644
--- a/core/java/com/android/internal/widget/TransportControlView.java
+++ b/core/java/com/android/internal/widget/TransportControlView.java
@@ -21,6 +21,8 @@
 import com.android.internal.widget.LockScreenWidgetCallback;
 import com.android.internal.widget.LockScreenWidgetInterface;
 
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -68,7 +70,7 @@
     private int mClientGeneration;
     private Metadata mMetadata = new Metadata();
     private boolean mAttached;
-    private ComponentName mClientName;
+    private PendingIntent mClientIntent;
     private int mTransportControlFlags;
     private int mPlayState;
     private AudioManager mAudioManager;
@@ -116,7 +118,7 @@
                     }
                 }
                 mClientGeneration = msg.arg1;
-                mClientName = (ComponentName) msg.obj;
+                mClientIntent = (PendingIntent) msg.obj;
                 break;
 
             }
@@ -174,12 +176,12 @@
             }
         }
 
-        public void setCurrentClientId(int clientGeneration, ComponentName clientEventReceiver,
+        public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
                 boolean clearing) throws RemoteException {
             Handler handler = mLocalHandler.get();
             if (handler != null) {
                 handler.obtainMessage(MSG_SET_GENERATION_ID,
-                    clientGeneration, (clearing ? 1 : 0), clientEventReceiver).sendToTarget();
+                    clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget();
             }
         }
     };
@@ -365,16 +367,27 @@
     }
 
     private void sendMediaButtonClick(int keyCode) {
-        // TODO: target to specific player based on mClientName
+        // use the registered PendingIntent that will be processed by the registered
+        //    media button event receiver, which is the component of mClientIntent
         KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
         Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
         intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-        getContext().sendOrderedBroadcast(intent, null);
+        try {
+            mClientIntent.send(getContext(), 0, intent);
+        } catch (CanceledException e) {
+            Log.e(TAG, "Error sending intent for media button down: "+e);
+            e.printStackTrace();
+        }
 
         keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
         intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
         intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-        getContext().sendOrderedBroadcast(intent, null);
+        try {
+            mClientIntent.send(getContext(), 0, intent);
+        } catch (CanceledException e) {
+            Log.e(TAG, "Error sending intent for media button up: "+e);
+            e.printStackTrace();
+        }
     }
 
     public void setCallback(LockScreenWidgetCallback callback) {
diff --git a/core/res/res/drawable-hdpi/scrubber_control_disabled_holo.png b/core/res/res/drawable-hdpi/scrubber_control_disabled_holo.png
index a231195..e0f55bf 100644
--- a/core/res/res/drawable-hdpi/scrubber_control_disabled_holo.png
+++ b/core/res/res/drawable-hdpi/scrubber_control_disabled_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/scrubber_control_focused_holo.png b/core/res/res/drawable-hdpi/scrubber_control_focused_holo.png
new file mode 100644
index 0000000..7b264f1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/scrubber_control_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/scrubber_control_holo.png b/core/res/res/drawable-hdpi/scrubber_control_holo.png
deleted file mode 100644
index fae05e5..0000000
--- a/core/res/res/drawable-hdpi/scrubber_control_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/scrubber_control_normal_holo.png b/core/res/res/drawable-hdpi/scrubber_control_normal_holo.png
new file mode 100644
index 0000000..c3a5f7d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/scrubber_control_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/scrubber_control_pressed_holo.png b/core/res/res/drawable-hdpi/scrubber_control_pressed_holo.png
index ff4d710..6fa4e00 100644
--- a/core/res/res/drawable-hdpi/scrubber_control_pressed_holo.png
+++ b/core/res/res/drawable-hdpi/scrubber_control_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_disabled_holo.png b/core/res/res/drawable-mdpi/scrubber_control_disabled_holo.png
index 9d7b77c..a035f6f 100644
--- a/core/res/res/drawable-mdpi/scrubber_control_disabled_holo.png
+++ b/core/res/res/drawable-mdpi/scrubber_control_disabled_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_focused_holo.png b/core/res/res/drawable-mdpi/scrubber_control_focused_holo.png
new file mode 100644
index 0000000..5c9720f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/scrubber_control_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_holo.png b/core/res/res/drawable-mdpi/scrubber_control_holo.png
deleted file mode 100644
index 832fa07..0000000
--- a/core/res/res/drawable-mdpi/scrubber_control_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_normal_holo.png b/core/res/res/drawable-mdpi/scrubber_control_normal_holo.png
new file mode 100644
index 0000000..2497716
--- /dev/null
+++ b/core/res/res/drawable-mdpi/scrubber_control_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/scrubber_control_pressed_holo.png b/core/res/res/drawable-mdpi/scrubber_control_pressed_holo.png
index 4a518f2..5fc3ca2 100644
--- a/core/res/res/drawable-mdpi/scrubber_control_pressed_holo.png
+++ b/core/res/res/drawable-mdpi/scrubber_control_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_disabled_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_disabled_holo.png
index 0b0bf24..d70151a 100644
--- a/core/res/res/drawable-xhdpi/scrubber_control_disabled_holo.png
+++ b/core/res/res/drawable-xhdpi/scrubber_control_disabled_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_focused_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_focused_holo.png
new file mode 100644
index 0000000..017688f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/scrubber_control_focused_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_holo.png
deleted file mode 100644
index 45060cb..0000000
--- a/core/res/res/drawable-xhdpi/scrubber_control_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_normal_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_normal_holo.png
new file mode 100644
index 0000000..727fcf1
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/scrubber_control_normal_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/scrubber_control_pressed_holo.png b/core/res/res/drawable-xhdpi/scrubber_control_pressed_holo.png
index d1fe115..6d03e42 100644
--- a/core/res/res/drawable-xhdpi/scrubber_control_pressed_holo.png
+++ b/core/res/res/drawable-xhdpi/scrubber_control_pressed_holo.png
Binary files differ
diff --git a/core/res/res/drawable/scrubber_control_selector_holo.xml b/core/res/res/drawable/scrubber_control_selector_holo.xml
index d146eee..1e6c668 100644
--- a/core/res/res/drawable/scrubber_control_selector_holo.xml
+++ b/core/res/res/drawable/scrubber_control_selector_holo.xml
@@ -15,7 +15,8 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:state_enabled="true" android:drawable="@android:drawable/scrubber_control_pressed_holo" />
-    <item android:state_enabled="true" android:drawable="@android:drawable/scrubber_control_holo" />
-    <item                              android:drawable="@android:drawable/scrubber_control_disabled_holo" />
+    <item android:state_enabled="false" android:drawable="@android:drawable/scrubber_control_disabled_holo" />
+    <item android:state_pressed="true" android:drawable="@android:drawable/scrubber_control_pressed_holo" />
+    <item android:state_selected="true" android:drawable="@android:drawable/scrubber_control_focused_holo" />
+    <item android:drawable="@android:drawable/scrubber_control_normal_holo" />
 </selector>
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index a5ba57d..3de75ba 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -1020,6 +1020,8 @@
             return true;
         }
     }
+    *outVx = 0;
+    *outVy = 0;
     return false;
 }
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index cd8bb1d..a0881a7 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -18,8 +18,10 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.os.Binder;
@@ -1684,17 +1686,42 @@
      * Register a component to be the sole receiver of MEDIA_BUTTON intents.
      * @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
      *      that will receive the media button intent. This broadcast receiver must be declared
-     *      in the application manifest.
+     *      in the application manifest. The package of the component must match that of
+     *      the context you're registering from.
      */
     public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
         if (eventReceiver == null) {
             return;
         }
+        if (!eventReceiver.getPackageName().equals(mContext.getPackageName())) {
+            Log.e(TAG, "registerMediaButtonEventReceiver() error: " +
+                    "receiver and context package names don't match");
+            return;
+        }
+        // construct a PendingIntent for the media button and register it
+        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        //     the associated intent will be handled by the component being registered
+        mediaButtonIntent.setComponent(eventReceiver);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext,
+                0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
+        registerMediaButtonIntent(pi, eventReceiver);
+    }
+
+    /**
+     * @hide
+     * no-op if (pi == null) or (eventReceiver == null)
+     */
+    public void registerMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) {
+        if ((pi == null) || (eventReceiver == null)) {
+            Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
+            return;
+        }
         IAudioService service = getService();
         try {
-            service.registerMediaButtonEventReceiver(eventReceiver);
+            // pi != null
+            service.registerMediaButtonIntent(pi, eventReceiver);
         } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e);
+            Log.e(TAG, "Dead object in registerMediaButtonIntent"+e);
         }
     }
 
@@ -1707,14 +1734,26 @@
         if (eventReceiver == null) {
             return;
         }
-        IAudioService service = getService();
-        try {
-            service.unregisterMediaButtonEventReceiver(eventReceiver);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e);
-        }
+        // construct a PendingIntent for the media button and unregister it
+        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        //     the associated intent will be handled by the component being registered
+        mediaButtonIntent.setComponent(eventReceiver);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext,
+                0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
+        unregisterMediaButtonIntent(pi, eventReceiver);
     }
 
+    /**
+     * @hide
+     */
+    public void unregisterMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) {
+        IAudioService service = getService();
+        try {
+            service.unregisterMediaButtonIntent(pi, eventReceiver);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e);
+        }
+    }
 
     /**
      * Registers the remote control client for providing information to display on the remote
@@ -1724,14 +1763,13 @@
      * @see RemoteControlClient
      */
     public void registerRemoteControlClient(RemoteControlClient rcClient) {
-        if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
+        if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
             return;
         }
         IAudioService service = getService();
         try {
-            service.registerRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
+            service.registerRemoteControlClient(rcClient.getRcMediaIntent(),   /* mediaIntent   */
                     rcClient.getIRemoteControlClient(),                        /* rcClient      */
-                    rcClient.toString(),                                       /* clientName    */
                     // used to match media button event receiver and audio focus
                     mContext.getPackageName());                                /* packageName   */
         } catch (RemoteException e) {
@@ -1746,13 +1784,13 @@
      * @see #registerRemoteControlClient(RemoteControlClient)
      */
     public void unregisterRemoteControlClient(RemoteControlClient rcClient) {
-        if ((rcClient == null) || (rcClient.getRcEventReceiver() == null)) {
+        if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
             return;
         }
         IAudioService service = getService();
         try {
-            service.unregisterRemoteControlClient(rcClient.getRcEventReceiver(), /* eventReceiver */
-                    rcClient.getIRemoteControlClient());                         /* rcClient      */
+            service.unregisterRemoteControlClient(rcClient.getRcMediaIntent(), /* mediaIntent   */
+                    rcClient.getIRemoteControlClient());                       /* rcClient      */
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e);
         }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index db27cfd..8895c9e 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -18,6 +18,8 @@
 
 import android.app.ActivityManagerNative;
 import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -35,6 +37,7 @@
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
@@ -2084,7 +2087,7 @@
             }
         }
 
-        private void persistMediaButtonReceiver(ComponentName receiver) {
+        private void onHandlePersistMediaButtonReceiver(ComponentName receiver) {
             Settings.System.putString(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER,
                     receiver == null ? "" : receiver.flattenToString());
         }
@@ -2201,7 +2204,7 @@
                     break;
 
                 case MSG_PERSIST_MEDIABUTTONRECEIVER:
-                    persistMediaButtonReceiver( (ComponentName) msg.obj );
+                    onHandlePersistMediaButtonReceiver( (ComponentName) msg.obj );
                     break;
 
                 case MSG_RCDISPLAY_CLEAR:
@@ -2894,14 +2897,22 @@
                 }
                 synchronized(mRCStack) {
                     if (!mRCStack.empty()) {
-                        // create a new intent specifically aimed at the current registered listener
+                        // create a new intent to fill in the extras of the registered PendingIntent
                         Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-                        targetedIntent.putExtras(intent.getExtras());
-                        targetedIntent.setComponent(mRCStack.peek().mReceiverComponent);
-                        // trap the current broadcast
-                        abortBroadcast();
-                        //Log.v(TAG, " Sending intent" + targetedIntent);
-                        context.sendBroadcast(targetedIntent, null);
+                        Bundle extras = intent.getExtras();
+                        if (extras != null) {
+                            targetedIntent.putExtras(extras);
+                            // trap the current broadcast
+                            abortBroadcast();
+                            //Log.v(TAG, " Sending intent" + targetedIntent);
+                            // send the intent that was registered by the client
+                            try {
+                                mRCStack.peek().mMediaIntent.send(context, 0, targetedIntent);
+                            } catch (CanceledException e) {
+                                Log.e(TAG, "Error sending pending intent " + mRCStack.peek());
+                                e.printStackTrace();
+                            }
+                        }
                     }
                 }
             }
@@ -2936,18 +2947,18 @@
      */
     private class RcClientDeathHandler implements IBinder.DeathRecipient {
         private IBinder mCb; // To be notified of client's death
-        private ComponentName mRcEventReceiver;
+        private PendingIntent mMediaIntent;
 
-        RcClientDeathHandler(IBinder cb, ComponentName eventReceiver) {
+        RcClientDeathHandler(IBinder cb, PendingIntent pi) {
             mCb = cb;
-            mRcEventReceiver = eventReceiver;
+            mMediaIntent = pi;
         }
 
         public void binderDied() {
             Log.w(TAG, "  RemoteControlClient died");
             // remote control client died, make sure the displays don't use it anymore
             //  by setting its remote control client to null
-            registerRemoteControlClient(mRcEventReceiver, null, null, null/*ignored*/);
+            registerRemoteControlClient(mMediaIntent, null, null/*ignored*/);
         }
 
         public IBinder getBinder() {
@@ -2956,18 +2967,29 @@
     }
 
     private static class RemoteControlStackEntry {
-        /** the target for the ACTION_MEDIA_BUTTON events */
-        public ComponentName mReceiverComponent;// always non null
+        /**
+         * The target for the ACTION_MEDIA_BUTTON events.
+         * Always non null.
+         */
+        public PendingIntent mMediaIntent;
+        /**
+         * The registered media button event receiver.
+         * Always non null.
+         */
+        public ComponentName mReceiverComponent;
         public String mCallingPackageName;
-        public String mRcClientName;
         public int mCallingUid;
-
-        /** provides access to the information to display on the remote control */
+        /**
+         * Provides access to the information to display on the remote control.
+         * May be null (when a media button event receiver is registered,
+         *     but no remote control client has been registered) */
         public IRemoteControlClient mRcClient;
         public RcClientDeathHandler mRcClientDeathHandler;
 
-        public RemoteControlStackEntry(ComponentName r) {
-            mReceiverComponent = r;
+        /** precondition: mediaIntent != null, eventReceiver != null */
+        public RemoteControlStackEntry(PendingIntent mediaIntent, ComponentName eventReceiver) {
+            mMediaIntent = mediaIntent;
+            mReceiverComponent = eventReceiver;
             mCallingUid = -1;
             mRcClient = null;
         }
@@ -3003,7 +3025,8 @@
             Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
             while(stackIterator.hasNext()) {
                 RemoteControlStackEntry rcse = stackIterator.next();
-                pw.println("     receiver: " + rcse.mReceiverComponent +
+                pw.println("  pi: " + rcse.mMediaIntent +
+                        "  -- ercvr: " + rcse.mReceiverComponent +
                         "  -- client: " + rcse.mRcClient +
                         "  -- uid: " + rcse.mCallingUid);
             }
@@ -3035,7 +3058,6 @@
                     mAudioHandler.sendMessage(
                             mAudioHandler.obtainMessage(MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0,
                                     null));
-                    return;
                 } else if (oldTop != mRCStack.peek()) {
                     // the top of the stack has changed, save it in the system settings
                     // by posting a message to persist it
@@ -3049,25 +3071,32 @@
 
     /**
      * Helper function:
-     * Restore remote control receiver from the system settings
+     * Restore remote control receiver from the system settings.
      */
     private void restoreMediaButtonReceiver() {
         String receiverName = Settings.System.getString(mContentResolver,
                 Settings.System.MEDIA_BUTTON_RECEIVER);
         if ((null != receiverName) && !receiverName.isEmpty()) {
-            ComponentName receiverComponentName = ComponentName.unflattenFromString(receiverName);
-            registerMediaButtonEventReceiver(receiverComponentName);
+            ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName);
+            // construct a PendingIntent targeted to the restored component name
+            // for the media button and register it
+            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+            //     the associated intent will be handled by the component being registered
+            mediaButtonIntent.setComponent(eventReceiver);
+            PendingIntent pi = PendingIntent.getBroadcast(mContext,
+                    0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
+            registerMediaButtonIntent(pi, eventReceiver);
         }
-        // upon restoring (e.g. after boot), do we want to refresh all remotes?
     }
 
     /**
      * Helper function:
-     * Set the new remote control receiver at the top of the RC focus stack
+     * Set the new remote control receiver at the top of the RC focus stack.
+     * precondition: mediaIntent != null, target != null
      */
-    private void pushMediaButtonReceiver(ComponentName newReceiver) {
+    private void pushMediaButtonReceiver(PendingIntent mediaIntent, ComponentName target) {
         // already at top of stack?
-        if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) {
+        if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(mediaIntent)) {
             return;
         }
         Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
@@ -3075,31 +3104,32 @@
         boolean wasInsideStack = false;
         while(stackIterator.hasNext()) {
             rcse = (RemoteControlStackEntry)stackIterator.next();
-            if(rcse.mReceiverComponent.equals(newReceiver)) {
+            if(rcse.mMediaIntent.equals(mediaIntent)) {
                 wasInsideStack = true;
                 stackIterator.remove();
                 break;
             }
         }
         if (!wasInsideStack) {
-            rcse = new RemoteControlStackEntry(newReceiver);
+            rcse = new RemoteControlStackEntry(mediaIntent, target);
         }
         mRCStack.push(rcse);
 
         // post message to persist the default media button receiver
         mAudioHandler.sendMessage( mAudioHandler.obtainMessage(
-                MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, newReceiver/*obj*/) );
+                MSG_PERSIST_MEDIABUTTONRECEIVER, 0, 0, target/*obj*/) );
     }
 
     /**
      * Helper function:
-     * Remove the remote control receiver from the RC focus stack
+     * Remove the remote control receiver from the RC focus stack.
+     * precondition: pi != null
      */
-    private void removeMediaButtonReceiver(ComponentName newReceiver) {
+    private void removeMediaButtonReceiver(PendingIntent pi) {
         Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
         while(stackIterator.hasNext()) {
             RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
-            if(rcse.mReceiverComponent.equals(newReceiver)) {
+            if(rcse.mMediaIntent.equals(pi)) {
                 stackIterator.remove();
                 break;
             }
@@ -3110,8 +3140,8 @@
      * Helper function:
      * Called synchronized on mRCStack
      */
-    private boolean isCurrentRcController(ComponentName eventReceiver) {
-        if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(eventReceiver)) {
+    private boolean isCurrentRcController(PendingIntent pi) {
+        if (!mRCStack.empty() && mRCStack.peek().mMediaIntent.equals(pi)) {
             return true;
         }
         return false;
@@ -3124,12 +3154,12 @@
      * Update the remote control displays with the new "focused" client generation
      */
     private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
-            ComponentName newClientEventReceiver, boolean clearing) {
+            PendingIntent newMediaIntent, boolean clearing) {
         // NOTE: Only one IRemoteControlDisplay supported in this implementation
         if (mRcDisplay != null) {
             try {
                 mRcDisplay.setCurrentClientId(
-                        newClientGeneration, newClientEventReceiver, clearing);
+                        newClientGeneration, newMediaIntent, clearing);
             } catch (RemoteException e) {
                 Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc() "+e);
                 // if we had a display before, stop monitoring its death
@@ -3167,10 +3197,9 @@
      *    where the display should be cleared.
      */
     private void setNewRcClient_syncRcsCurrc(int newClientGeneration,
-            ComponentName newClientEventReceiver, boolean clearing) {
+            PendingIntent newMediaIntent, boolean clearing) {
         // send the new valid client generation ID to all displays
-        setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newClientEventReceiver,
-                clearing);
+        setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
         // send the new valid client generation ID to all clients
         setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
     }
@@ -3186,7 +3215,7 @@
                 mCurrentRcClientGen++;
                 // synchronously update the displays and clients with the new client generation
                 setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
-                        null /*event receiver*/, true /*clearing*/);
+                        null /*newMediaIntent*/, true /*clearing*/);
             }
         }
     }
@@ -3204,7 +3233,7 @@
                     // synchronously update the displays and clients with
                     //      the new client generation
                     setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
-                            rcse.mReceiverComponent /*event receiver*/,
+                            rcse.mMediaIntent /*newMediaIntent*/,
                             false /*clearing*/);
 
                     // tell the current client that it needs to send info
@@ -3301,27 +3330,34 @@
         updateRemoteControlDisplay_syncAfRcs(infoChangedFlags);
     }
 
-    /** see AudioManager.registerMediaButtonEventReceiver(ComponentName eventReceiver) */
-    public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
-        Log.i(TAG, "  Remote Control   registerMediaButtonEventReceiver() for " + eventReceiver);
+    /** 
+     * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
+     * precondition: mediaIntent != null, target != null
+     */
+    public void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver) {
+        Log.i(TAG, "  Remote Control   registerMediaButtonIntent() for " + mediaIntent);
 
         synchronized(mAudioFocusLock) {
             synchronized(mRCStack) {
-                pushMediaButtonReceiver(eventReceiver);
+                pushMediaButtonReceiver(mediaIntent, eventReceiver);
                 // new RC client, assume every type of information shall be queried
                 checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
             }
         }
     }
 
-    /** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */
-    public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
-        Log.i(TAG, "  Remote Control   unregisterMediaButtonEventReceiver() for " + eventReceiver);
+    /** 
+     * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
+     * precondition: mediaIntent != null, eventReceiver != null
+     */
+    public void unregisterMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver)
+    {
+        Log.i(TAG, "  Remote Control   unregisterMediaButtonIntent() for " + mediaIntent);
 
         synchronized(mAudioFocusLock) {
             synchronized(mRCStack) {
-                boolean topOfStackWillChange = isCurrentRcController(eventReceiver);
-                removeMediaButtonReceiver(eventReceiver);
+                boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
+                removeMediaButtonReceiver(mediaIntent);
                 if (topOfStackWillChange) {
                     // current RC client will change, assume every type of info needs to be queried
                     checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
@@ -3331,8 +3367,8 @@
     }
 
     /** see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) */
-    public void registerRemoteControlClient(ComponentName eventReceiver,
-            IRemoteControlClient rcClient, String clientName, String callingPackageName) {
+    public void registerRemoteControlClient(PendingIntent mediaIntent,
+            IRemoteControlClient rcClient, String callingPackageName) {
         if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
         synchronized(mAudioFocusLock) {
             synchronized(mRCStack) {
@@ -3340,7 +3376,7 @@
                 Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
                 while(stackIterator.hasNext()) {
                     RemoteControlStackEntry rcse = stackIterator.next();
-                    if(rcse.mReceiverComponent.equals(eventReceiver)) {
+                    if(rcse.mMediaIntent.equals(mediaIntent)) {
                         // already had a remote control client?
                         if (rcse.mRcClientDeathHandler != null) {
                             // stop monitoring the old client's death
@@ -3357,7 +3393,6 @@
                             }
                         }
                         rcse.mCallingPackageName = callingPackageName;
-                        rcse.mRcClientName = clientName;
                         rcse.mCallingUid = Binder.getCallingUid();
                         if (rcClient == null) {
                             rcse.mRcClientDeathHandler = null;
@@ -3366,7 +3401,7 @@
                         // monitor the new client's death
                         IBinder b = rcClient.asBinder();
                         RcClientDeathHandler rcdh =
-                                new RcClientDeathHandler(b, rcse.mReceiverComponent);
+                                new RcClientDeathHandler(b, rcse.mMediaIntent);
                         try {
                             b.linkToDeath(rcdh, 0);
                         } catch (RemoteException e) {
@@ -3380,7 +3415,7 @@
                 }
                 // if the eventReceiver is at the top of the stack
                 // then check for potential refresh of the remote controls
-                if (isCurrentRcController(eventReceiver)) {
+                if (isCurrentRcController(mediaIntent)) {
                     checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
                 }
             }
@@ -3388,24 +3423,23 @@
     }
 
     /**
-     * see AudioManager.unregisterRemoteControlClient(ComponentName eventReceiver, ...)
+     * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...)
      * rcClient is guaranteed non-null
      */
-    public void unregisterRemoteControlClient(ComponentName eventReceiver,
+    public void unregisterRemoteControlClient(PendingIntent mediaIntent,
             IRemoteControlClient rcClient) {
         synchronized(mAudioFocusLock) {
             synchronized(mRCStack) {
                 Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
                 while(stackIterator.hasNext()) {
                     RemoteControlStackEntry rcse = stackIterator.next();
-                    if ((rcse.mReceiverComponent.equals(eventReceiver))
+                    if ((rcse.mMediaIntent.equals(mediaIntent))
                             && rcClient.equals(rcse.mRcClient)) {
                         // we found the IRemoteControlClient to unregister
                         // stop monitoring its death
                         rcse.unlinkToRcClientDeath();
                         // reset the client-related fields
                         rcse.mRcClient = null;
-                        rcse.mRcClientName = null;
                         rcse.mRcClientDeathHandler = null;
                         rcse.mCallingPackageName = null;
                     }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7bf9814..5294d36 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.media.IAudioFocusDispatcher;
 import android.media.IRemoteControlClient;
@@ -85,13 +86,12 @@
     
     void unregisterAudioFocusClient(String clientId);
 
-    void registerMediaButtonEventReceiver(in ComponentName eventReceiver);
+    oneway void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c);
+    oneway void unregisterMediaButtonIntent(in PendingIntent pi,  in ComponentName c);
 
-    void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver);
-
-    oneway void registerRemoteControlClient(in ComponentName eventReceiver,
-           in IRemoteControlClient rcClient, in String clientName, in String callingPackageName);
-    oneway void unregisterRemoteControlClient(in ComponentName eventReceiver,
+    oneway void registerRemoteControlClient(in PendingIntent mediaIntent,
+           in IRemoteControlClient rcClient, in String callingPackageName);
+    oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
            in IRemoteControlClient rcClient);
 
     oneway void   registerRemoteControlDisplay(in IRemoteControlDisplay rcd);
diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl
index fd50b7e..e15b07c 100644
--- a/media/java/android/media/IRemoteControlDisplay.aidl
+++ b/media/java/android/media/IRemoteControlDisplay.aidl
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.graphics.Bitmap;
 import android.os.Bundle;
@@ -31,14 +32,12 @@
     /**
      * Sets the generation counter of the current client that is displayed on the remote control.
      * @param clientGeneration the new RemoteControlClient generation
-     * @param clientEventReceiver the media button event receiver associated with the client.
-     *    May be null, which implies there is no registered media button event receiver. This
-     *    parameter is supplied as an optimization so a display can directly target media button
-     *    events to the client.
+     * @param clientMediaIntent the PendingIntent associated with the client.
+     *    May be null, which implies there is no registered media button event receiver.
      * @param clearing true if the new client generation value maps to a remote control update
      *    where the display should be cleared.
      */
-    void setCurrentClientId(int clientGeneration, in ComponentName clientEventReceiver,
+    void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent,
             boolean clearing);
 
     void setPlaybackState(int generationId, int state);
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index cdebba0..5dea87f 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -34,6 +34,7 @@
 import java.lang.IllegalArgumentException;
 
 /**
+ * TODO javadoc update for ComponentName - PendingIntent change
  * RemoteControlClient enables exposing information meant to be consumed by remote controls
  * capable of displaying metadata, artwork and media transport control buttons.
  * A remote control client object is associated with a media button event receiver. This
@@ -205,50 +206,6 @@
     public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
 
     /**
-     * @hide
-     * TODO remove after modifying known (internal) media apps using this API
-     * Class constructor.
-     * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have
-     *     been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
-     *     before this new RemoteControlClient can itself be registered with
-     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
-     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
-     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
-     */
-    public RemoteControlClient(ComponentName mediaButtonEventReceiver) {
-        mRcEventReceiver = mediaButtonEventReceiver;
-
-        Looper looper;
-        if ((looper = Looper.myLooper()) != null) {
-            mEventHandler = new EventHandler(this, looper);
-        } else if ((looper = Looper.getMainLooper()) != null) {
-            mEventHandler = new EventHandler(this, looper);
-        } else {
-            mEventHandler = null;
-            Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
-        }
-    }
-
-    /**
-     * @hide
-     * TODO remove after modifying known (internal) media apps using this API
-     * Class constructor for a remote control client whose internal event handling
-     * happens on a user-provided Looper.
-     * @param mediaButtonEventReceiver The receiver for the media button events. It needs to have
-     *     been registered with {@link AudioManager#registerMediaButtonEventReceiver(ComponentName)}
-     *     before this new RemoteControlClient can itself be registered with
-     *     {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.
-     * @param looper The Looper running the event loop.
-     * @see AudioManager#registerMediaButtonEventReceiver(ComponentName)
-     * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
-     */
-    public RemoteControlClient(ComponentName mediaButtonEventReceiver, Looper looper) {
-        mRcEventReceiver = mediaButtonEventReceiver;
-
-        mEventHandler = new EventHandler(this, looper);
-    }
-
-    /**
      * Class constructor.
      * @param mediaButtonIntent The intent that will be sent for the media button events sent
      *     by remote controls.
@@ -262,8 +219,7 @@
      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
      */
     public RemoteControlClient(PendingIntent mediaButtonIntent) {
-        // TODO implement using PendingIntent instead of ComponentName
-        mRcEventReceiver = null;
+        mRcMediaIntent = mediaButtonIntent;
 
         Looper looper;
         if ((looper = Looper.myLooper()) != null) {
@@ -292,8 +248,7 @@
      * @see AudioManager#registerRemoteControlClient(RemoteControlClient)
      */
     public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
-        // TODO implement using PendingIntent instead of ComponentName
-        mRcEventReceiver = null;
+        mRcMediaIntent = mediaButtonIntent;
 
         mEventHandler = new EventHandler(this, looper);
     }
@@ -614,9 +569,10 @@
     private int mInternalClientGenId = -2;
 
     /**
-     * The media button event receiver associated with this remote control client
+     * The media button intent description associated with this remote control client
+     * (can / should include target component for intent handling)
      */
-    private final ComponentName mRcEventReceiver;
+    private final PendingIntent mRcMediaIntent;
 
     /**
      * The remote control display to which this client will send information.
@@ -626,10 +582,10 @@
 
     /**
      * @hide
-     * Accessor to media button event receiver
+     * Accessor to media button intent description (includes target component)
      */
-    public ComponentName getRcEventReceiver() {
-        return mRcEventReceiver;
+    public PendingIntent getRcMediaIntent() {
+        return mRcMediaIntent;
     }
     /**
      * @hide
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
index f84cc19..2fe22ff 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
@@ -69,6 +69,12 @@
 
     </FrameLayout>
 
+    <include layout="@layout/status_bar_no_recent_apps"
+        android:id="@+id/recents_no_apps"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible" />
+
     <View android:id="@+id/recents_dismiss_button"
         android:layout_width="80px"
         android:layout_height="@*android:dimen/status_bar_height"
diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
index ed9ea7a..4d49077 100644
--- a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
@@ -67,6 +67,12 @@
 
     </FrameLayout>
 
+    <include layout="@layout/status_bar_no_recent_apps"
+        android:id="@+id/recents_no_apps"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible" />
+
     <View android:id="@+id/recents_dismiss_button"
         android:layout_width="80px"
         android:layout_height="@*android:dimen/status_bar_height"
diff --git a/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml b/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml
new file mode 100644
index 0000000..47ffb83
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    >
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24dp"
+        android:textColor="#ffffffff"
+        android:text="@string/status_bar_no_recent_apps"
+        android:gravity="center_horizontal"
+        android:layout_gravity="center"
+    />
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index bad7e1f..b9e6d78 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -41,6 +41,10 @@
     <!-- Title shown in recents popup for inspecting an application's properties -->
     <string name="status_bar_recent_inspect_item_title">App info</string>
 
+    <!-- Message shown in the middle of the screen after clicking on the recent apps button
+         when there are no recent apps to show [CHAR LIMIT=45]-->
+    <string name="status_bar_no_recent_apps">No recent apps</string>
+
     <!-- The label in the bar at the top of the status bar when there are no notifications
          showing.  [CHAR LIMIT=40]-->
     <string name="status_bar_no_notifications_title">No notifications</string>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
index 9749a1d..fbf00d2 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
@@ -38,17 +38,20 @@
     View mRootView;
     View mScrimView;
     View mContentView;
+    View mNoRecentAppsView;
     AnimatorSet mContentAnim;
     Animator.AnimatorListener mListener;
 
     // the panel will start to appear this many px from the end
     final int HYPERSPACE_OFFRAMP = 200;
 
-    public Choreographer(View root, View scrim, View content, Animator.AnimatorListener listener) {
+    public Choreographer(View root, View scrim, View content,
+            View noRecentApps, Animator.AnimatorListener listener) {
         mRootView = root;
         mScrimView = scrim;
         mContentView = content;
         mListener = listener;
+        mNoRecentAppsView = noRecentApps;
     }
 
     void createAnimation(boolean appearing) {
@@ -81,8 +84,24 @@
                 : new android.view.animation.DecelerateInterpolator(1.0f));
         glowAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
 
+        Animator noRecentAppsFadeAnim = null;
+        if (mNoRecentAppsView != null &&  // doesn't exist on large devices
+                mNoRecentAppsView.getVisibility() == View.VISIBLE) {
+            noRecentAppsFadeAnim = ObjectAnimator.ofFloat(mNoRecentAppsView, "alpha",
+                    mContentView.getAlpha(), appearing ? 1.0f : 0.0f);
+            noRecentAppsFadeAnim.setInterpolator(appearing
+                    ? new android.view.animation.AccelerateInterpolator(1.0f)
+                    : new android.view.animation.DecelerateInterpolator(1.0f));
+            noRecentAppsFadeAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
+        }
+
         mContentAnim = new AnimatorSet();
         final Builder builder = mContentAnim.play(glowAnim).with(posAnim);
+
+        if (noRecentAppsFadeAnim != null) {
+            builder.with(noRecentAppsFadeAnim);
+        }
+
         Drawable background = mScrimView.getBackground();
         if (background != null) {
             Animator bgAnim = ObjectAnimator.ofInt(background,
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 0621b22..6fdc534 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -83,6 +83,7 @@
     private int mIconDpi;
     private View mRecentsScrim;
     private View mRecentsGlowView;
+    private View mRecentsNoApps;
     private ViewGroup mRecentsContainer;
     private Bitmap mDefaultThumbnailBackground;
 
@@ -373,8 +374,9 @@
 
 
         mRecentsGlowView = findViewById(R.id.recents_glow);
-        mRecentsScrim = (View) findViewById(R.id.recents_bg_protect);
-        mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this);
+        mRecentsScrim = findViewById(R.id.recents_bg_protect);
+        mRecentsNoApps = findViewById(R.id.recents_no_apps);
+        mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, mRecentsNoApps, this);
         mRecentsDismissButton = findViewById(R.id.recents_dismiss_button);
         mRecentsDismissButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
@@ -581,6 +583,9 @@
             mThumbnailLoader.cancel(false);
             mThumbnailLoader = null;
         }
+        if (mRecentsNoApps != null) { // doesn't exist on large devices
+            mRecentsNoApps.setVisibility(View.INVISIBLE);
+        }
         mActivityDescriptions = getRecentTasks();
         for (ActivityDescription ad : mActivityDescriptions) {
             ad.setThumbnail(mDefaultThumbnailBackground);
@@ -647,7 +652,11 @@
         } else {
             // Immediately hide this panel
             if (DEBUG) Log.v(TAG, "Nothing to show");
-            hide(false);
+            if (mRecentsNoApps != null) { // doesn't exist on large devices
+                mRecentsNoApps.setVisibility(View.VISIBLE);
+            } else {
+                hide(false);
+            }
         }
     }
 
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 2348d76..e0a2adc 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -2394,6 +2394,12 @@
         return mTethering.getTetheredIfaces();
     }
 
+    @Override
+    public String[] getTetheredIfacePairs() {
+        enforceTetherAccessPermission();
+        return mTethering.getTetheredIfacePairs();
+    }
+
     public String[] getTetheringErroredIfaces() {
         enforceTetherAccessPermission();
         return mTethering.getErroredIfaces();
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index f0b5958..99f6b8e 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -142,5 +142,5 @@
 # ---------------------------
 # NetworkStatsService.java
 # ---------------------------
-51100 netstats_mobile_sample (iface_rx|2|2),(iface_tx|2|2),(uid_rx|2|2),(uid_tx|2|2)
-51101 netstats_wifi_sample (iface_rx|2|2),(iface_tx|2|2),(uid_rx|2|2),(uid_tx|2|2)
+51100 netstats_mobile_sample (iface_rx_bytes|2|2),(iface_tx_bytes|2|2),(iface_rx_pkts|2|1),(iface_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1)
+51101 netstats_wifi_sample (iface_rx_bytes|2|2),(iface_tx_bytes|2|2),(iface_rx_pkts|2|1),(iface_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1)
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index c517965..0cffb15 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -123,6 +123,8 @@
         public static final int InterfaceTxCounterResult  = 217;
         public static final int InterfaceRxThrottleResult = 218;
         public static final int InterfaceTxThrottleResult = 219;
+        public static final int QuotaCounterResult        = 220;
+        public static final int TetheringStatsResult      = 221;
 
         public static final int InterfaceChange           = 600;
         public static final int BandwidthControl          = 601;
@@ -1443,6 +1445,73 @@
         return stats;
     }
 
+    @Override
+    public NetworkStats getNetworkStatsTethering(String[] ifacePairs) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+
+        if (ifacePairs.length % 2 != 0) {
+            throw new IllegalArgumentException(
+                    "unexpected ifacePairs; length=" + ifacePairs.length);
+        }
+
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
+        for (int i = 0; i < ifacePairs.length; i += 2) {
+            final String ifaceIn = ifacePairs[i];
+            final String ifaceOut = ifacePairs[i + 1];
+            if (ifaceIn != null && ifaceOut != null) {
+                stats.combineValues(getNetworkStatsTethering(ifaceIn, ifaceOut));
+            }
+        }
+        return stats;
+    }
+
+    private NetworkStats.Entry getNetworkStatsTethering(String ifaceIn, String ifaceOut) {
+        final StringBuilder command = new StringBuilder();
+        command.append("bandwidth gettetherstats ").append(ifaceIn).append(" ").append(ifaceOut);
+
+        final String rsp;
+        try {
+            rsp = mConnector.doCommand(command.toString()).get(0);
+        } catch (NativeDaemonConnectorException e) {
+            throw new IllegalStateException("Error communicating to native daemon", e);
+        }
+
+        final String[] tok = rsp.split(" ");
+        /* Expecting: "code ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets" */
+        if (tok.length != 7) {
+            throw new IllegalStateException("Native daemon returned unexpected result: " + rsp);
+        }
+
+        final int code;
+        try {
+            code = Integer.parseInt(tok[0]);
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException(
+                    "Failed to parse native daemon return code for " + ifaceIn + " " + ifaceOut);
+        }
+        if (code != NetdResponseCode.TetheringStatsResult) {
+            throw new IllegalStateException(
+                    "Unexpected return code from native daemon for " + ifaceIn + " " + ifaceOut);
+        }
+
+        try {
+            final NetworkStats.Entry entry = new NetworkStats.Entry();
+            entry.iface = ifaceIn;
+            entry.uid = UID_ALL;
+            entry.set = SET_DEFAULT;
+            entry.tag = TAG_NONE;
+            entry.rxBytes = Long.parseLong(tok[3]);
+            entry.rxPackets = Long.parseLong(tok[4]);
+            entry.txBytes = Long.parseLong(tok[5]);
+            entry.txPackets = Long.parseLong(tok[6]);
+            return entry;
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException(
+                    "problem parsing tethering stats for " + ifaceIn + " " + ifaceOut + ": " + e);
+        }
+    }
+
     public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index ae8b89d..5286824 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -19,7 +19,6 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.bluetooth.BluetoothPan;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,15 +27,14 @@
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
 import android.net.ConnectivityManager;
-import android.net.InterfaceConfiguration;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
+import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
 import android.net.NetworkUtils;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -51,6 +49,7 @@
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.google.android.collect.Lists;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -59,8 +58,8 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.Set;
+
 /**
  * @hide
  *
@@ -68,7 +67,6 @@
  *
  * TODO - look for parent classes and code sharing
  */
-
 public class Tethering extends INetworkManagementEventObserver.Stub {
 
     private Context mContext;
@@ -629,6 +627,19 @@
         return retVal;
     }
 
+    public String[] getTetheredIfacePairs() {
+        final ArrayList<String> list = Lists.newArrayList();
+        synchronized (mIfaces) {
+            for (TetherInterfaceSM sm : mIfaces.values()) {
+                if (sm.isTethered()) {
+                    list.add(sm.mMyUpstreamIfaceName);
+                    list.add(sm.mIfaceName);
+                }
+            }
+        }
+        return list.toArray(new String[list.size()]);
+    }
+
     public String[] getTetherableIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mIfaces) {
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index a2b097e..947cf9c 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -25,6 +25,7 @@
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
@@ -34,7 +35,7 @@
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.UID_REMOVED;
-import static android.provider.Settings.Secure.NETSTATS_FORCE_COMPLETE_POLL;
+import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
@@ -68,6 +69,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.TrafficStats;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -89,6 +91,7 @@
 import com.android.internal.os.AtomicFile;
 import com.android.internal.util.Objects;
 import com.android.server.EventLogTags;
+import com.android.server.connectivity.Tethering;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 import com.google.android.collect.Sets;
@@ -132,14 +135,10 @@
     private static final int MSG_PERFORM_POLL = 0x1;
 
     /** Flags to control detail level of poll event. */
-    private static final int FLAG_POLL_NETWORK = 0x1;
-    private static final int FLAG_POLL_UID = 0x2;
     private static final int FLAG_PERSIST_NETWORK = 0x10;
     private static final int FLAG_PERSIST_UID = 0x20;
-    private static final int FLAG_FORCE_PERSIST = 0x100;
-
-    private static final int FLAG_POLL_ALL = FLAG_POLL_NETWORK | FLAG_POLL_UID;
     private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
+    private static final int FLAG_PERSIST_FORCE = 0x100;
 
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
@@ -177,7 +176,6 @@
         public long getUidMaxHistory();
         public long getTagMaxHistory();
         public long getTimeCacheMaxAge();
-        public boolean getForceCompletePoll();
     }
 
     private final Object mStatsLock = new Object();
@@ -195,6 +193,7 @@
     private NetworkStats mLastPollNetworkSnapshot;
     private NetworkStats mLastPollUidSnapshot;
     private NetworkStats mLastPollOperationsSnapshot;
+    private NetworkStats mLastPollTetherSnapshot;
 
     private NetworkStats mLastPersistNetworkSnapshot;
     private NetworkStats mLastPersistUidSnapshot;
@@ -258,6 +257,10 @@
         final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE);
         mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
 
+        // watch for tethering changes
+        final IntentFilter tetherFilter = new IntentFilter(ACTION_TETHER_STATE_CHANGED);
+        mContext.registerReceiver(mTetherReceiver, tetherFilter, CONNECTIVITY_INTERNAL, mHandler);
+
         // listen for periodic polling events
         final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
         mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
@@ -519,7 +522,7 @@
     @Override
     public void forceUpdate() {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
-        performPoll(FLAG_POLL_ALL | FLAG_PERSIST_ALL);
+        performPoll(FLAG_PERSIST_ALL);
     }
 
     /**
@@ -543,12 +546,24 @@
         }
     };
 
+    /**
+     * Receiver that watches for {@link Tethering} to claim interface pairs.
+     */
+    private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified CONNECTIVITY_INTERNAL
+            // permission above.
+            performPoll(FLAG_PERSIST_NETWORK);
+        }
+    };
+
     private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and verified UPDATE_DEVICE_STATS
             // permission above.
-            performPoll(FLAG_POLL_ALL | FLAG_PERSIST_ALL);
+            performPoll(FLAG_PERSIST_ALL);
 
             // verify that we're watching global alert
             registerGlobalAlert();
@@ -595,7 +610,7 @@
             if (LIMIT_GLOBAL_ALERT.equals(limitName)) {
                 // kick off background poll to collect network stats; UID stats
                 // are handled during normal polling interval.
-                final int flags = FLAG_POLL_NETWORK | FLAG_PERSIST_NETWORK;
+                final int flags = FLAG_PERSIST_NETWORK;
                 mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget();
 
                 // re-arm global alert for next update
@@ -617,10 +632,9 @@
         // isn't perfect, since the kernel may already be counting traffic from
         // the updated network.
 
-        // poll both network and UID stats, but only persist network stats,
-        // since this codepath should stay fast. UID stats will be persisted
-        // during next alarm poll event.
-        performPollLocked(FLAG_POLL_ALL | FLAG_PERSIST_NETWORK);
+        // poll, but only persist network stats to keep codepath fast. UID stats
+        // will be persisted during next alarm poll event.
+        performPollLocked(FLAG_PERSIST_NETWORK);
 
         final NetworkState[] states;
         try {
@@ -684,19 +698,9 @@
         if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
         final long startRealtime = SystemClock.elapsedRealtime();
 
-        boolean pollNetwork = (flags & FLAG_POLL_NETWORK) != 0;
-        boolean pollUid = (flags & FLAG_POLL_UID) != 0;
-
-        // when complete poll requested, any partial poll enables everything
-        final boolean forceCompletePoll = mSettings.getForceCompletePoll();
-        if (forceCompletePoll && (pollNetwork || pollUid)) {
-            pollNetwork = true;
-            pollUid = true;
-        }
-
         final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
         final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
-        final boolean forcePersist = (flags & FLAG_FORCE_PERSIST) != 0;
+        final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0;
 
         // try refreshing time source when stale
         if (mTime.getCacheAge() > mSettings.getTimeCacheMaxAge()) {
@@ -709,32 +713,36 @@
         final long threshold = mSettings.getPersistThreshold();
 
         try {
-            if (pollNetwork) {
-                final NetworkStats networkSnapshot = mNetworkManager.getNetworkStatsSummary();
-                performNetworkPollLocked(networkSnapshot, currentTime);
+            // record network stats
+            final NetworkStats networkSnapshot = mNetworkManager.getNetworkStatsSummary();
+            performNetworkPollLocked(networkSnapshot, currentTime);
 
-                // persist when enough network data has occurred
-                final NetworkStats persistNetworkDelta = computeStatsDelta(
-                        mLastPersistNetworkSnapshot, networkSnapshot, true);
-                final boolean pastThreshold = persistNetworkDelta.getTotalBytes() > threshold;
-                if (forcePersist || (persistNetwork && pastThreshold)) {
-                    writeNetworkStatsLocked();
-                    mLastPersistNetworkSnapshot = networkSnapshot;
-                }
+            // persist when enough network data has occurred
+            final NetworkStats persistNetworkDelta = computeStatsDelta(
+                    mLastPersistNetworkSnapshot, networkSnapshot, true);
+            final boolean networkPastThreshold = persistNetworkDelta.getTotalBytes() > threshold;
+            if (persistForce || (persistNetwork && networkPastThreshold)) {
+                writeNetworkStatsLocked();
+                mLastPersistNetworkSnapshot = networkSnapshot;
             }
 
-            if (pollUid) {
-                final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
-                performUidPollLocked(uidSnapshot, currentTime);
+            // record tethering stats; persisted during normal UID cycle below
+            final String[] ifacePairs = mConnManager.getTetheredIfacePairs();
+            final NetworkStats tetherSnapshot = mNetworkManager.getNetworkStatsTethering(
+                    ifacePairs);
+            performTetherPollLocked(tetherSnapshot, currentTime);
 
-                // persist when enough network data has occurred
-                final NetworkStats persistUidDelta = computeStatsDelta(
-                        mLastPersistUidSnapshot, uidSnapshot, true);
-                final boolean pastThreshold = persistUidDelta.getTotalBytes() > threshold;
-                if (forcePersist || (persistUid && pastThreshold)) {
-                    writeUidStatsLocked();
-                    mLastPersistUidSnapshot = uidSnapshot;
-                }
+            // record uid stats
+            final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
+            performUidPollLocked(uidSnapshot, currentTime);
+
+            // persist when enough network data has occurred
+            final NetworkStats persistUidDelta = computeStatsDelta(
+                    mLastPersistUidSnapshot, uidSnapshot, true);
+            final boolean uidPastThreshold = persistUidDelta.getTotalBytes() > threshold;
+            if (persistForce || (persistUid && uidPastThreshold)) {
+                writeUidStatsLocked();
+                mLastPersistUidSnapshot = uidSnapshot;
             }
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem reading network stats", e);
@@ -748,9 +756,7 @@
         }
 
         // sample stats after each full poll
-        if (pollNetwork && pollUid) {
-            performSample();
-        }
+        performSample();
 
         // finally, dispatch updated event to any listeners
         final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
@@ -780,12 +786,6 @@
             history.recordData(timeStart, currentTime, entry);
         }
 
-        // trim any history beyond max
-        final long maxHistory = mSettings.getNetworkMaxHistory();
-        for (NetworkStatsHistory history : mNetworkStats.values()) {
-            history.removeBucketsBefore(currentTime - maxHistory);
-        }
-
         mLastPollNetworkSnapshot = networkSnapshot;
 
         if (LOGD && unknownIface.size() > 0) {
@@ -829,32 +829,54 @@
             history.recordData(timeStart, currentTime, entry);
         }
 
-        // trim any history beyond max
-        final long maxUidHistory = mSettings.getUidMaxHistory();
-        final long maxTagHistory = mSettings.getTagMaxHistory();
-        for (UidStatsKey key : mUidStats.keySet()) {
-            final NetworkStatsHistory history = mUidStats.get(key);
-
-            // detailed tags are trimmed sooner than summary in TAG_NONE
-            if (key.tag == TAG_NONE) {
-                history.removeBucketsBefore(currentTime - maxUidHistory);
-            } else {
-                history.removeBucketsBefore(currentTime - maxTagHistory);
-            }
-        }
-
         mLastPollUidSnapshot = uidSnapshot;
         mLastPollOperationsSnapshot = mOperations;
         mOperations = new NetworkStats(0L, 10);
     }
 
     /**
+     * Update {@link #mUidStats} historical usage for
+     * {@link TrafficStats#UID_TETHERING} based on tethering statistics.
+     */
+    private void performTetherPollLocked(NetworkStats tetherSnapshot, long currentTime) {
+        ensureUidStatsLoadedLocked();
+
+        final NetworkStats delta = computeStatsDelta(
+                mLastPollTetherSnapshot, tetherSnapshot, false);
+        final long timeStart = currentTime - delta.getElapsedRealtime();
+
+        NetworkStats.Entry entry = null;
+        for (int i = 0; i < delta.size(); i++) {
+            entry = delta.getValues(i, entry);
+            final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
+            if (ident == null) {
+                if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
+                        || entry.txPackets > 0) {
+                    Log.w(TAG, "dropping tether delta from unknown iface: " + entry);
+                }
+                continue;
+            }
+
+            final NetworkStatsHistory history = findOrCreateUidStatsLocked(
+                    ident, UID_TETHERING, SET_DEFAULT, TAG_NONE);
+            history.recordData(timeStart, currentTime, entry);
+        }
+
+        // normal UID poll will trim any history beyond max
+        mLastPollTetherSnapshot = tetherSnapshot;
+    }
+
+    /**
      * Sample recent statistics summary into {@link EventLog}.
      */
     private void performSample() {
-        // take sample as total over last 4 hours
-        final long end = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis();
-        final long start = end - (4 * HOUR_IN_MILLIS);
+        final long largestBucketSize = Math.max(
+                mSettings.getNetworkBucketDuration(), mSettings.getUidBucketDuration());
+
+        // take sample as atomic buckets
+        final long now = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis();
+        final long end = now - (now % largestBucketSize) + largestBucketSize;
+        final long start = end - largestBucketSize;
 
         NetworkTemplate template = null;
         NetworkStats.Entry ifaceTotal = null;
@@ -864,15 +886,17 @@
         template = buildTemplateMobileAll(getActiveSubscriberId(mContext));
         ifaceTotal = getSummaryForNetwork(template, start, end).getTotal(ifaceTotal);
         uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
-        EventLogTags.writeNetstatsMobileSample(
-                ifaceTotal.rxBytes, ifaceTotal.txBytes, uidTotal.rxBytes, uidTotal.txBytes);
+        EventLogTags.writeNetstatsMobileSample(ifaceTotal.rxBytes, ifaceTotal.rxPackets,
+                ifaceTotal.txBytes, ifaceTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets,
+                uidTotal.txBytes, uidTotal.rxPackets);
 
         // collect wifi sample
         template = buildTemplateWifi();
         ifaceTotal = getSummaryForNetwork(template, start, end).getTotal(ifaceTotal);
         uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
-        EventLogTags.writeNetstatsWifiSample(
-                ifaceTotal.rxBytes, ifaceTotal.txBytes, uidTotal.rxBytes, uidTotal.txBytes);
+        EventLogTags.writeNetstatsWifiSample(ifaceTotal.rxBytes, ifaceTotal.rxPackets,
+                ifaceTotal.txBytes, ifaceTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets,
+                uidTotal.txBytes, uidTotal.rxPackets);
     }
 
     /**
@@ -1072,6 +1096,15 @@
 
         // TODO: consider duplicating stats and releasing lock while writing
 
+        // trim any history beyond max
+        if (mTime.hasCache()) {
+            final long currentTime = mTime.currentTimeMillis();
+            final long maxHistory = mSettings.getNetworkMaxHistory();
+            for (NetworkStatsHistory history : mNetworkStats.values()) {
+                history.removeBucketsBefore(currentTime - maxHistory);
+            }
+        }
+
         FileOutputStream fos = null;
         try {
             fos = mNetworkFile.startWrite();
@@ -1107,6 +1140,23 @@
 
         // TODO: consider duplicating stats and releasing lock while writing
 
+        // trim any history beyond max
+        if (mTime.hasCache()) {
+            final long currentTime = mTime.currentTimeMillis();
+            final long maxUidHistory = mSettings.getUidMaxHistory();
+            final long maxTagHistory = mSettings.getTagMaxHistory();
+            for (UidStatsKey key : mUidStats.keySet()) {
+                final NetworkStatsHistory history = mUidStats.get(key);
+
+                // detailed tags are trimmed sooner than summary in TAG_NONE
+                if (key.tag == TAG_NONE) {
+                    history.removeBucketsBefore(currentTime - maxUidHistory);
+                } else {
+                    history.removeBucketsBefore(currentTime - maxTagHistory);
+                }
+            }
+        }
+
         // build UidStatsKey lists grouped by ident
         final HashMap<NetworkIdentitySet, ArrayList<UidStatsKey>> keysByIdent = Maps.newHashMap();
         for (UidStatsKey key : mUidStats.keySet()) {
@@ -1171,7 +1221,7 @@
             }
 
             if (argSet.contains("poll")) {
-                performPollLocked(FLAG_POLL_ALL | FLAG_PERSIST_ALL | FLAG_FORCE_PERSIST);
+                performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE);
                 pw.println("Forced poll");
                 return;
             }
@@ -1399,8 +1449,5 @@
         public long getTimeCacheMaxAge() {
             return DAY_IN_MILLIS;
         }
-        public boolean getForceCompletePoll() {
-            return getSecureBoolean(NETSTATS_FORCE_COMPLETE_POLL, false);
-        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 2b1eea1..99ae027 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -272,7 +272,11 @@
 
         // graceful shutdown system, which should trigger persist of stats, and
         // clear any values in memory.
+        expectCurrentTime();
+        expectDefaultSettings();
+        replay();
         mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+        verifyAndReset();
 
         // talk with zombie service to assert stats have gone; and assert that
         // we persisted them to file.
@@ -487,6 +491,7 @@
 
         // now pretend two UIDs are uninstalled, which should migrate stats to
         // special "removed" bucket.
+        expectCurrentTime();
         expectDefaultSettings();
         replay();
         final Intent intent = new Intent(ACTION_UID_REMOVED);
@@ -758,7 +763,6 @@
         expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
         expect(mSettings.getTagMaxHistory()).andReturn(maxHistory).anyTimes();
         expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
-        expect(mSettings.getForceCompletePoll()).andReturn(false).anyTimes();
     }
 
     private void expectCurrentTime() throws Exception {
diff --git a/telephony/java/com/android/internal/telephony/ApnContext.java b/telephony/java/com/android/internal/telephony/ApnContext.java
index a52f1ca..8aeee87 100644
--- a/telephony/java/com/android/internal/telephony/ApnContext.java
+++ b/telephony/java/com/android/internal/telephony/ApnContext.java
@@ -154,6 +154,12 @@
         return mState;
     }
 
+    public boolean isDisconnected() {
+        DataConnectionTracker.State currentState = getState();
+        return ((currentState == DataConnectionTracker.State.IDLE) ||
+                    currentState == DataConnectionTracker.State.FAILED);
+    }
+
     public synchronized void setReason(String reason) {
         if (DBG) {
             log("set reason as " + reason + ", for type " + mApnType + ",current state " + mState);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 6d0b696..dd8a60a 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -787,9 +787,7 @@
         }
         DataConnectionAc dcac = apnContext.getDataConnectionAc();
         if (tearDown) {
-            boolean isConnected = (apnContext.getState() != State.IDLE
-                                   && apnContext.getState() != State.FAILED);
-            if (!isConnected) {
+            if (apnContext.isDisconnected()) {
                 // The request is tearDown and but ApnContext is not connected.
                 // If apnContext is not enabled anymore, break the linkage to the DCAC/DC.
                 apnContext.setState(State.IDLE);
@@ -1019,11 +1017,9 @@
      */
     private void onApnChanged() {
         // TODO: How to handle when multiple APNs are active?
-        boolean isConnected;
 
         ApnContext defaultApnContext = mApnContexts.get(Phone.APN_TYPE_DEFAULT);
-        isConnected = (defaultApnContext.getState() != State.IDLE
-                       && defaultApnContext.getState() != State.FAILED);
+        boolean defaultApnIsDisconnected = defaultApnContext.isDisconnected();
 
         if (mPhone instanceof GSMPhone) {
             // The "current" may no longer be valid.  MMS depends on this to send properly. TBD
@@ -1034,8 +1030,8 @@
         // match the current operator.
         if (DBG) log("onApnChanged: createAllApnList and cleanUpAllConnections");
         createAllApnList();
-        cleanUpAllConnections(isConnected, Phone.REASON_APN_CHANGED);
-        if (!isConnected) {
+        cleanUpAllConnections(!defaultApnIsDisconnected, Phone.REASON_APN_CHANGED);
+        if (defaultApnIsDisconnected) {
             setupDataOnReadyApns(Phone.REASON_APN_CHANGED);
         }
     }
@@ -1885,7 +1881,7 @@
 
         // if all data connection are gone, check whether Airplane mode request was
         // pending.
-        if (!isConnected()) {
+        if (isDisconnected()) {
             if (mPhone.getServiceStateTracker().processPendingRadioPowerOffAfterDataOff()) {
                 // Radio will be turned off. No need to retry data setup
                 apnContext.setApnSetting(null);
@@ -1957,12 +1953,25 @@
     protected boolean isConnected() {
         for (ApnContext apnContext : mApnContexts.values()) {
             if (apnContext.getState() == State.CONNECTED) {
-            return true;
+                // At least one context is connected, return true
+                return true;
             }
         }
+        // There are not any contexts connected, return false
         return false;
     }
 
+    protected boolean isDisconnected() {
+        for (ApnContext apnContext : mApnContexts.values()) {
+            if (!apnContext.isDisconnected()) {
+                // At least one context was not disconnected return false
+                return false;
+            }
+        }
+        // All contexts were disconnected so return true
+        return true;
+    }
+
     @Override
     protected void notifyDataConnection(String reason) {
         if (DBG) log("notifyDataConnection: reason=" + reason);