Merge "Support for "streaming" non-transport streams by first converting them to ts packets."
diff --git a/api/current.txt b/api/current.txt
index 90890d6..d90dbab 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9397,6 +9397,7 @@
     method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo);
     method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo);
     method public void onUpdateSelection(int, int, int, int, int, int);
+    method public void onViewClicked(boolean);
     method public void onWindowHidden();
     method public void onWindowShown();
     method public void requestHideSelf(int);
@@ -9440,6 +9441,7 @@
     method public void updateCursor(android.graphics.Rect);
     method public void updateExtractedText(int, android.view.inputmethod.ExtractedText);
     method public void updateSelection(int, int, int, int, int, int);
+    method public void viewClicked(boolean);
   }
 
   public static final class InputMethodService.Insets {
@@ -11240,7 +11242,8 @@
     method public static long getUidUdpRxPackets(int);
     method public static long getUidUdpTxBytes(int);
     method public static long getUidUdpTxPackets(int);
-    method public static void setThreadStatsTag(java.lang.String);
+    method public static void setThreadStatsTag(int);
+    method public static deprecated void setThreadStatsTag(java.lang.String);
     method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
     method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
     field public static final int UNSUPPORTED = -1; // 0xffffffff
@@ -11446,11 +11449,14 @@
   }
 
   public class SslError {
-    ctor public SslError(int, android.net.http.SslCertificate);
-    ctor public SslError(int, java.security.cert.X509Certificate);
+    ctor public deprecated SslError(int, android.net.http.SslCertificate);
+    ctor public deprecated SslError(int, java.security.cert.X509Certificate);
+    ctor public SslError(int, android.net.http.SslCertificate, java.lang.String);
+    ctor public SslError(int, java.security.cert.X509Certificate, java.lang.String);
     method public boolean addError(int);
     method public android.net.http.SslCertificate getCertificate();
     method public int getPrimaryError();
+    method public java.lang.String getUrl();
     method public boolean hasError(int);
     field public static final int SSL_EXPIRED = 1; // 0x1
     field public static final int SSL_IDMISMATCH = 2; // 0x2
@@ -23507,6 +23513,7 @@
     method public void updateCursor(android.view.View, int, int, int, int);
     method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText);
     method public void updateSelection(android.view.View, int, int, int, int);
+    method public void viewClicked(android.view.View);
     field public static final int HIDE_IMPLICIT_ONLY = 1; // 0x1
     field public static final int HIDE_NOT_ALWAYS = 2; // 0x2
     field public static final int RESULT_HIDDEN = 3; // 0x3
@@ -23527,6 +23534,7 @@
     method public abstract void updateCursor(android.graphics.Rect);
     method public abstract void updateExtractedText(int, android.view.inputmethod.ExtractedText);
     method public abstract void updateSelection(int, int, int, int, int, int);
+    method public abstract void viewClicked(boolean);
   }
 
   public static abstract interface InputMethodSession.EventCallback {
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index df8cf9a..e10f218 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -47,6 +47,7 @@
     private static final int DO_APP_PRIVATE_COMMAND = 100;
     private static final int DO_TOGGLE_SOFT_INPUT = 105;
     private static final int DO_FINISH_SESSION = 110;
+    private static final int DO_VIEW_CLICKED = 115;
 
     HandlerCaller mCaller;
     InputMethodSession mInputMethodSession;
@@ -133,6 +134,10 @@
                 mInputMethodSession = null;
                 return;
             }
+            case DO_VIEW_CLICKED: {
+                mInputMethodSession.viewClicked(msg.arg1 == 1);
+                return;
+            }
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
@@ -167,7 +172,11 @@
                 oldSelStart, oldSelEnd, newSelStart, newSelEnd,
                 candidatesStart, candidatesEnd));
     }
-    
+
+    public void viewClicked(boolean focusChanged) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
+    }
+
     public void updateCursor(Rect newCursor) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UPDATE_CURSOR,
                 newCursor));
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index feb246e..9481a88 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -488,7 +488,15 @@
             InputMethodService.this.onUpdateSelection(oldSelStart, oldSelEnd,
                     newSelStart, newSelEnd, candidatesStart, candidatesEnd);
         }
-        
+
+        @Override
+        public void viewClicked(boolean focusChanged) {
+            if (!isEnabled()) {
+                return;
+            }
+            InputMethodService.this.onViewClicked(focusChanged);
+        }
+
         /**
          * Call {@link InputMethodService#onUpdateCursor
          * InputMethodService.onUpdateCursor()}.
@@ -1609,6 +1617,16 @@
     }
 
     /**
+     * Called when the user tapped or clicked a text view.
+     * IMEs can't rely on this method being called because this was not part of the original IME
+     * protocol, so applications with custom text editing written before this method appeared will
+     * not call to inform the IME of this interaction.
+     * @param focusChanged true if the user changed the focused view by this click.
+     */
+    public void onViewClicked(boolean focusChanged) {
+    }
+
+    /**
      * Called when the application has reported a new location of its text
      * cursor.  This is only called if explicitly requested by the input method.
      * The default implementation does nothing.
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 91af16d..21fad2c 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -16,14 +16,21 @@
 
 package android.net;
 
+import static android.content.pm.PackageManager.GET_SIGNATURES;
 import static android.text.format.Time.MONTH_DAY;
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
 import android.os.RemoteException;
 import android.text.format.Time;
 
+import com.google.android.collect.Sets;
+
 import java.io.PrintWriter;
+import java.util.HashSet;
 
 /**
  * Manager for creating and modifying network policy rules.
@@ -210,8 +217,35 @@
      * usually to protect critical system services.
      */
     public static boolean isUidValidForPolicy(Context context, int uid) {
-        return (uid >= android.os.Process.FIRST_APPLICATION_UID
-                && uid <= android.os.Process.LAST_APPLICATION_UID);
+        // first, quick-reject non-applications
+        if (uid < android.os.Process.FIRST_APPLICATION_UID
+                || uid > android.os.Process.LAST_APPLICATION_UID) {
+            return false;
+        }
+
+        final PackageManager pm = context.getPackageManager();
+        final HashSet<Signature> systemSignature;
+        try {
+            systemSignature = Sets.newHashSet(
+                    pm.getPackageInfo("android", GET_SIGNATURES).signatures);
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException("problem finding system signature", e);
+        }
+
+        try {
+            // reject apps signed with system cert
+            for (String packageName : pm.getPackagesForUid(uid)) {
+                final HashSet<Signature> packageSignature = Sets.newHashSet(
+                        pm.getPackageInfo(packageName, GET_SIGNATURES).signatures);
+                if (packageSignature.containsAll(systemSignature)) {
+                    return false;
+                }
+            }
+        } catch (NameNotFoundException e) {
+        }
+
+        // nothing found above; we can apply policy to UID
+        return true;
     }
 
     /** {@hide} */
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index cb47193..2b59dba 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -16,14 +16,18 @@
 
 package android.net;
 
+import android.app.DownloadManager;
+import android.app.backup.BackupManager;
 import android.content.Context;
+import android.media.MediaPlayer;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
-import dalvik.system.BlockGuard;
+import com.android.server.NetworkManagementSocketTagger;
 
+import dalvik.system.SocketTagger;
 import java.net.Socket;
 import java.net.SocketException;
 
@@ -50,6 +54,27 @@
     public static final int UID_REMOVED = -4;
 
     /**
+     * Default tag value for {@link DownloadManager} traffic.
+     *
+     * @hide
+     */
+    public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFF0001;
+
+    /**
+     * Default tag value for {@link MediaPlayer} traffic.
+     *
+     * @hide
+     */
+    public static final int TAG_SYSTEM_MEDIA = 0xFFFF0002;
+
+    /**
+     * Default tag value for {@link BackupManager} traffic.
+     *
+     * @hide
+     */
+    public static final int TAG_SYSTEM_BACKUP = 0xFFFF0003;
+
+    /**
      * Snapshot of {@link NetworkStats} when the currently active profiling
      * session started, or {@code null} if no session active.
      *
@@ -67,12 +92,20 @@
      * Changes only take effect during subsequent calls to
      * {@link #tagSocket(Socket)}.
      */
+    public static void setThreadStatsTag(int tag) {
+        NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+    }
+
+    /**
+     * @deprecated unsupported, will eventually be removed
+     */
+    @Deprecated
     public static void setThreadStatsTag(String tag) {
-        BlockGuard.setThreadSocketStatsTag(tag);
+        setThreadStatsTag(tag.hashCode());
     }
 
     public static void clearThreadStatsTag() {
-        BlockGuard.setThreadSocketStatsTag(null);
+        NetworkManagementSocketTagger.setThreadSocketStatsTag(-1);
     }
 
     /**
@@ -89,12 +122,12 @@
      * {@hide}
      */
     public static void setThreadStatsUid(int uid) {
-        BlockGuard.setThreadSocketStatsUid(uid);
+        NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
     }
 
     /** {@hide} */
     public static void clearThreadStatsUid() {
-        BlockGuard.setThreadSocketStatsUid(-1);
+        NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
     }
 
     /**
@@ -103,18 +136,18 @@
      * parameters. When finished, call {@link #untagSocket(Socket)} to remove
      * statistics parameters.
      *
-     * @see #setThreadStatsTag(String)
+     * @see #setThreadStatsTag(int)
      * @see #setThreadStatsUid(int)
      */
     public static void tagSocket(Socket socket) throws SocketException {
-        BlockGuard.tagSocketFd(socket.getFileDescriptor$());
+        SocketTagger.get().tag(socket);
     }
 
     /**
      * Remove any statistics parameters from the given {@link Socket}.
      */
     public static void untagSocket(Socket socket) throws SocketException {
-        BlockGuard.untagSocketFd(socket.getFileDescriptor$());
+        SocketTagger.get().untag(socket);
     }
 
     /**
diff --git a/core/java/android/net/http/SslError.java b/core/java/android/net/http/SslError.java
index e1b9deb..1e1cb49 100644
--- a/core/java/android/net/http/SslError.java
+++ b/core/java/android/net/http/SslError.java
@@ -59,36 +59,97 @@
     /**
      * The SSL certificate associated with the error set
      */
-    SslCertificate mCertificate;
+    final SslCertificate mCertificate;
+
+    /**
+     * The URL associated with the error set.
+     */
+    final String mUrl;
 
     /**
      * Creates a new SSL error set object
      * @param error The SSL error
      * @param certificate The associated SSL certificate
+     * @deprecated Use {@link #SslError(int, SslCertificate, String)}
      */
+    @Deprecated
     public SslError(int error, SslCertificate certificate) {
         addError(error);
+        if (certificate == null) {
+            throw new NullPointerException("certificate is null.");
+        }
         mCertificate = certificate;
+        mUrl = "";
     }
 
     /**
      * Creates a new SSL error set object
      * @param error The SSL error
      * @param certificate The associated SSL certificate
+     * @deprecated Use {@link #SslError(int, X509Certificate, String)}
      */
+    @Deprecated
     public SslError(int error, X509Certificate certificate) {
         addError(error);
+        if (certificate == null) {
+            throw new NullPointerException("certificate is null.");
+        }
         mCertificate = new SslCertificate(certificate);
+        mUrl = "";
     }
 
     /**
-     * @return The SSL certificate associated with the error set
+     * Creates a new SSL error set object
+     * @param error The SSL error
+     * @param certificate The associated SSL certificate
+     * @param url The associated URL.
+     */
+    public SslError(int error, SslCertificate certificate, String url) {
+        addError(error);
+        if (certificate == null) {
+            throw new NullPointerException("certificate is null.");
+        }
+        mCertificate = certificate;
+        if (url == null) {
+            throw new NullPointerException("url is null.");
+        }
+        mUrl = url;
+    }
+
+    /**
+     * Creates a new SSL error set object
+     * @param error The SSL error
+     * @param certificate The associated SSL certificate
+     * @param url The associated URL.
+     */
+    public SslError(int error, X509Certificate certificate, String url) {
+        addError(error);
+        if (certificate == null) {
+            throw new NullPointerException("certificate is null.");
+        }
+        mCertificate = new SslCertificate(certificate);
+        if (url == null) {
+            throw new NullPointerException("url is null.");
+        }
+        mUrl = url;
+    }
+
+    /**
+     * @return The SSL certificate associated with the error set, non-null.
      */
     public SslCertificate getCertificate() {
         return mCertificate;
     }
 
     /**
+     * @return The URL associated with the error set, non-null.
+     * "" if one of the deprecated constructors is used.
+     */
+    public String getUrl() {
+        return mUrl;
+    }
+
+    /**
      * Adds the SSL error to the error set
      * @param error The SSL error to add
      * @return True iff the error being added is a known SSL error
@@ -137,6 +198,7 @@
      */
     public String toString() {
         return "primary error: " + getPrimaryError() +
-            " certificate: " + getCertificate();
+            " certificate: " + getCertificate() +
+            "  on URL: " + getUrl();
     }
 }
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 0933193..39c6f57 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -57,6 +57,29 @@
                 Uri.parse("content://call_log/calls/filter");
 
         /**
+         * An optional URI parameter which instructs the provider to allow the operation to be
+         * applied to voicemail records as well.
+         * <p>
+         * TYPE: Boolean
+         * <p>
+         * Using this parameter with a value of {@code true} will result in a security error if the
+         * calling package does not have appropriate permissions to access voicemails.
+         *
+         * @hide
+         */
+        public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails";
+
+        /**
+         * Content uri with {@link #ALLOW_VOICEMAILS_PARAM_KEY} set. This can directly be used to
+         * access call log entries that includes voicemail records.
+         *
+         * @hide
+         */
+        public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon()
+                .appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true")
+                .build();
+
+        /**
          * The default sort order for this table
          */
         public static final String DEFAULT_SORT_ORDER = "date DESC";
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 49f3bbe..9b081b2 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -239,7 +239,7 @@
                     break;
             }
         } finally {
-            finishEvent(false);
+            finishEvent();
         }
     }
 
@@ -302,7 +302,7 @@
                 problem("Source was not SOURCE_CLASS_TRACKBALL.");
             }
         } finally {
-            finishEvent(false);
+            finishEvent();
         }
     }
 
@@ -328,7 +328,9 @@
             mTouchEventStreamUnhandled = false;
             mTouchEventStreamPointers = 0;
         }
-        final boolean wasTainted = mTouchEventStreamIsTainted;
+        if (mTouchEventStreamIsTainted) {
+            event.setTainted(true);
+        }
 
         try {
             ensureMetaStateIsNormalized(event.getMetaState());
@@ -441,7 +443,7 @@
                 problem("Source was not SOURCE_CLASS_POINTER.");
             }
         } finally {
-            finishEvent(wasTainted);
+            finishEvent();
         }
     }
 
@@ -499,7 +501,7 @@
                 }
             }
         } finally {
-            finishEvent(false);
+            finishEvent();
         }
     }
 
@@ -591,9 +593,9 @@
         return true;
     }
 
-    private void finishEvent(boolean tainted) {
+    private void finishEvent() {
         if (mViolationMessage != null && mViolationMessage.length() != 0) {
-            if (!tainted) {
+            if (!mCurrentEvent.isTainted()) {
                 // Write a log message only if the event was not already tainted.
                 mViolationMessage.append("\n  in ").append(mCaller);
                 mViolationMessage.append("\n  ");
@@ -614,17 +616,14 @@
                 }
 
                 Log.d(mLogTag, mViolationMessage.toString());
-                tainted = true;
+
+                // Taint the event so that we do not generate additional violations from it
+                // further downstream.
+                mCurrentEvent.setTainted(true);
             }
             mViolationMessage.setLength(0);
         }
 
-        if (tainted) {
-            // Taint the event so that we do not generate additional violations from it
-            // further downstream.
-            mCurrentEvent.setTainted(true);
-        }
-
         if (RECENT_EVENTS_TO_LOG != 0) {
             if (mRecentEvents == null) {
                 mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index f45e78b..88f59d4 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2885,7 +2885,7 @@
                     toolTypeToString(getToolType(i)));
         }
 
-        msg.append(", buttonState=").append(KeyEvent.metaStateToString(getButtonState()));
+        msg.append(", buttonState=").append(MotionEvent.buttonStateToString(getButtonState()));
         msg.append(", metaState=").append(KeyEvent.metaStateToString(getMetaState()));
         msg.append(", flags=0x").append(Integer.toHexString(getFlags()));
         msg.append(", edgeFlags=0x").append(Integer.toHexString(getEdgeFlags()));
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 47f5e4c..a1a7281 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1126,7 +1126,7 @@
         if (mServedView == mNextServedView && !mNextServedNeedsStart) {
             return;
         }
-        
+
         InputConnection ic = null;
         synchronized (mH) {
             if (mServedView == mNextServedView && !mNextServedNeedsStart) {
@@ -1242,6 +1242,27 @@
     }
 
     /**
+     * Notify the event when the user tapped or clicked the text view.
+     */
+    public void viewClicked(View view) {
+        final boolean focusChanged = mServedView != mNextServedView;
+        checkFocus();
+        synchronized (mH) {
+            if ((mServedView != view && (mServedView == null
+                    || !mServedView.checkInputConnectionProxy(view)))
+                    || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+                return;
+            }
+            try {
+                if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
+                mCurMethod.viewClicked(focusChanged);
+            } catch (RemoteException e) {
+                Log.w(TAG, "IME died: " + mCurId, e);
+            }
+        }
+    }
+
+    /**
      * Returns true if the current input method wants to watch the location
      * of the input editor's cursor in its window.
      */
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index bb03afa..ea6f5ee 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -63,6 +63,15 @@
             int candidatesStart, int candidatesEnd);
 
     /**
+     * This method is called when the user tapped a text view.
+     * IMEs can't rely on this method being called because this was not part of the original IME
+     * protocol, so applications with custom text editing written before this method appeared will
+     * not call to inform the IME of this interaction.
+     * @param focusChanged true if the user changed the focused view by this click.
+     */
+    public void viewClicked(boolean focusChanged);
+
+    /**
      * This method is called when cursor location of the target input field
      * has changed within its window.  This is not normally called, but will
      * only be reported if requested by the input method.
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 2f4774f..79a5aff 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -1150,11 +1150,12 @@
      * {@link #nativeSslCertErrorProceed(int)} or
      * {@link #nativeSslCertErrorCancel(int, int)}.
      */
-    private void reportSslCertError(final int handle, final int cert_error, byte cert_der[]) {
+    private void reportSslCertError(
+            final int handle, final int cert_error, byte cert_der[], String url) {
         final SslError ssl_error;
         try {
             X509Certificate cert = new X509CertImpl(cert_der);
-            ssl_error = new SslError(cert_error, cert);
+            ssl_error = new SslError(cert_error, cert, url);
         } catch (IOException e) {
             // Can't get the certificate, not much to do.
             Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 2947ebe..712ecca 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -549,11 +549,13 @@
         final ValueAnimator fadeScroller = ObjectAnimator.ofInt(this, "selectorPaintAlpha", 255, 0);
         final ObjectAnimator showIncrementButton = ObjectAnimator.ofFloat(mIncrementButton,
                 "alpha", 0, 1);
+        final ObjectAnimator showInputText = ObjectAnimator.ofFloat(mInputText,
+                "alpha", 0, 1);
         final ObjectAnimator showDecrementButton = ObjectAnimator.ofFloat(mDecrementButton,
                 "alpha", 0, 1);
         mShowInputControlsAnimator = new AnimatorSet();
         mShowInputControlsAnimator.playTogether(fadeScroller, showIncrementButton,
-                showDecrementButton);
+                showInputText, showDecrementButton);
         mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() {
             private boolean mCanceled = false;
 
@@ -1205,13 +1207,11 @@
         }
         int[] selectorIndices = getSelectorIndices();
         int totalTextHeight = selectorIndices.length * mTextSize;
-        int totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
-        int textGapCount = selectorIndices.length - 1;
-        int selectorTextGapHeight = totalTextGapHeight / textGapCount;
-        // compensate for integer division loss of the components used to
-        // calculate the text gap
-        int integerDivisionLoss = (mTextSize + mBottom - mTop) % textGapCount;
-        mInitialScrollOffset = mCurrentScrollOffset = mTextSize - integerDivisionLoss / 2;
+        float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
+        float textGapCount = selectorIndices.length - 1;
+        int selectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
+        // Compensate if text size is odd since every time we get its middle a pixel is lost.
+        mInitialScrollOffset = mCurrentScrollOffset = mTextSize - (3 * (mTextSize % 2));
         mSelectorElementHeight = mTextSize + selectorTextGapHeight;
         updateInputTextView();
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 85e7eec..2f9bd69 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4948,7 +4948,10 @@
                         if (mMovement != null && mText instanceof Editable
                                 && mLayout != null && onCheckIsTextEditor()) {
                             InputMethodManager imm = InputMethodManager.peekInstance();
-                            if (imm != null) imm.showSoftInput(this, 0);
+                            if (imm != null) {
+                                imm.viewClicked(this);
+                                imm.showSoftInput(this, 0);
+                            }
                         }
                     }
                 }
@@ -7398,8 +7401,11 @@
 
             if ((isTextEditable() || mTextIsSelectable) && touchIsFinished) {
                 // Show the IME, except when selecting in read-only text.
+                final InputMethodManager imm = InputMethodManager.peekInstance();
+                if (imm != null) {
+                    imm.viewClicked(this);
+                }
                 if (!mTextIsSelectable) {
-                    final InputMethodManager imm = InputMethodManager.peekInstance();
                     handled |= imm != null && imm.showSoftInput(this, 0);
                 }
 
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 5e9cd23..f13e770 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -25,16 +25,13 @@
 import android.os.SystemProperties;
 import android.util.Log;
 import android.util.Slog;
-
 import com.android.internal.logging.AndroidConfig;
-
+import com.android.server.NetworkManagementSocketTagger;
 import dalvik.system.VMRuntime;
-
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.util.logging.LogManager;
 import java.util.TimeZone;
-
+import java.util.logging.LogManager;
 import org.apache.harmony.luni.internal.util.TimezoneGetter;
 
 /**
@@ -129,6 +126,11 @@
         System.setProperty("http.agent", userAgent);
 
         /*
+         * Wire socket tagging to traffic stats.
+         */
+        NetworkManagementSocketTagger.install();
+
+        /*
          * If we're running in an emulator launched with "-trace", put the
          * VM into emulator trace profiling mode so that the user can hit
          * F9/F10 at any time to capture traces.  This has performance
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index 338dcaa..f875cbd 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -36,7 +36,9 @@
     void updateSelection(int oldSelStart, int oldSelEnd,
             int newSelStart, int newSelEnd,
             int candidatesStart, int candidatesEnd);
-    
+
+    void viewClicked(boolean focusChanged);
+
     void updateCursor(in Rect newCursor);
     
     void displayCompletions(in CompletionInfo[] completions);
diff --git a/core/java/com/android/server/NetworkManagementSocketTagger.java b/core/java/com/android/server/NetworkManagementSocketTagger.java
new file mode 100644
index 0000000..306d223
--- /dev/null
+++ b/core/java/com/android/server/NetworkManagementSocketTagger.java
@@ -0,0 +1,165 @@
+/*
+ * 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.server;
+
+import dalvik.system.SocketTagger;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.SocketException;
+import java.nio.charset.Charsets;
+
+/**
+ * Assigns tags to sockets for traffic stats.
+ */
+public final class NetworkManagementSocketTagger extends SocketTagger {
+
+    private static final boolean LOGI = false;
+
+    private static ThreadLocal<SocketTags> threadSocketTags = new ThreadLocal<SocketTags>() {
+        @Override protected SocketTags initialValue() {
+            return new SocketTags();
+        }
+    };
+
+    public static void install() {
+        SocketTagger.set(new NetworkManagementSocketTagger());
+    }
+
+    public static void setThreadSocketStatsTag(int tag) {
+        threadSocketTags.get().statsTag = tag;
+    }
+
+    public static void setThreadSocketStatsUid(int uid) {
+        threadSocketTags.get().statsUid = uid;
+    }
+
+    @Override public void tag(FileDescriptor fd) throws SocketException {
+        final SocketTags options = threadSocketTags.get();
+        if (LOGI) {
+            System.logI("tagSocket(" + fd.getInt$() + ") with statsTag="
+                    + options.statsTag + ", statsUid=" + options.statsUid);
+        }
+        try {
+            // TODO: skip tagging when options would be no-op
+            tagSocketFd(fd, options.statsTag, options.statsUid);
+        } catch (IOException e) {
+            throw new SocketException("Problem tagging socket", e);
+        }
+    }
+
+    private void tagSocketFd(FileDescriptor fd, int tag, int uid) throws IOException {
+        final int fdNum = fd.getInt$();
+        if (fdNum == -1 || (tag == -1 && uid == -1)) return;
+
+        String cmd = "t " + fdNum;
+        if (tag == -1) {
+            // Case where just the uid needs adjusting. But probably the caller
+            // will want to track his own name here, just in case.
+            cmd += " 0";
+        } else {
+            cmd += " " + tagToKernel(tag);
+        }
+        if (uid != -1) {
+            cmd += " " + uid;
+        }
+        internalModuleCtrl(cmd);
+    }
+
+    @Override public void untag(FileDescriptor fd) throws SocketException {
+        if (LOGI) {
+            System.logI("untagSocket(" + fd.getInt$() + ")");
+        }
+        try {
+            unTagSocketFd(fd);
+        } catch (IOException e) {
+            throw new SocketException("Problem untagging socket", e);
+        }
+    }
+
+    private void unTagSocketFd(FileDescriptor fd) throws IOException {
+        int fdNum = fd.getInt$();
+        if (fdNum == -1) return;
+        String cmd = "u " + fdNum;
+        internalModuleCtrl(cmd);
+    }
+
+    public static class SocketTags {
+        public int statsTag = -1;
+        public int statsUid = -1;
+    }
+
+    /**
+     * Sends commands to the kernel netfilter module.
+     *
+     * @param cmd command string for the qtaguid netfilter module. May not be null.
+     *   <p>Supports:
+     *     <ul><li>tag a socket:<br>
+     *        <code>t <i>sock_fd</i> <i>acct_tag</i> [<i>uid_in_case_caller_is_acting_on_behalf_of</i>]</code><br>
+     *     <code>*_tag</code> defaults to default_policy_tag_from_uid(uid_of_caller)<br>
+     *     <code>acct_tag</code> is either 0 or greater that 2^32.<br>
+     *     <code>uid_*</code> is only settable by privileged UIDs (DownloadManager,...)
+     *     </li>
+     *     <li>untag a socket, preserving counters:<br>
+     *       <code>u <i>sock_fd</i></code>
+     *     </li></ul>
+     *   <p>Notes:<br>
+     *   <ul><li><i>sock_fd</i> is withing the callers process space.</li>
+     *   <li><i>*_tag</i> are 64bit values</li></ul>
+     *
+     */
+    private void internalModuleCtrl(String cmd) throws IOException {
+        final FileOutputStream procOut;
+        // TODO: Use something like
+        //  android.os.SystemProperties.getInt("persist.bandwidth.enable", 0)
+        // to see if tagging should happen or not.
+        try {
+            procOut = new FileOutputStream("/proc/net/xt_qtaguid/ctrl");
+        } catch (FileNotFoundException e) {
+            if (LOGI) {
+                System.logI("Can't talk to kernel module:" + e);
+            }
+            return;
+        }
+        try {
+            procOut.write(cmd.getBytes(Charsets.US_ASCII));
+        } finally {
+            procOut.close();
+        }
+    }
+
+    /**
+     * Convert {@link Integer} tag to {@code /proc/} format. Assumes unsigned
+     * base-10 format like {@code 2147483647}. Currently strips signed bit to
+     * avoid using {@link java.math.BigInteger}.
+     */
+    public static String tagToKernel(int tag) {
+        // TODO: eventually write in hex, since that's what proc exports
+        // TODO: migrate to direct integer instead of odd shifting
+        return Long.toString((((long) tag) << 32) & 0x7FFFFFFF00000000L);
+    }
+
+    /**
+     * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
+     * format like {@code 0x7fffffff00000000}.
+     */
+    public static int kernelToTag(String string) {
+        // TODO: migrate to direct integer instead of odd shifting
+        return (int) (Long.decode(string) >> 32);
+    }
+}
diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png
deleted file mode 100644
index 1f7aeee..0000000
--- a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png
deleted file mode 100755
index 4c1d89d..0000000
--- a/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_pressed_holo_light.9.png
deleted file mode 100644
index 6963a0e..0000000
--- a/core/res/res/drawable-hdpi/btn_toggle_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_holo_dark.9.png b/core/res/res/drawable-hdpi/cab_holo_dark.9.png
deleted file mode 100755
index 64e2052..0000000
--- a/core/res/res/drawable-hdpi/cab_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png b/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png
deleted file mode 100644
index 3a84de9..0000000
--- a/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png
deleted file mode 100644
index 48cc017..0000000
--- a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png
deleted file mode 100755
index 66cbe48..0000000
--- a/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_pressed_holo_light.9.png
deleted file mode 100644
index b6508fc..0000000
--- a/core/res/res/drawable-mdpi/btn_toggle_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_holo_dark.9.png b/core/res/res/drawable-mdpi/cab_holo_dark.9.png
deleted file mode 100755
index 7daae1f..0000000
--- a/core/res/res/drawable-mdpi/cab_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png b/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png
deleted file mode 100644
index cb3d0f2..0000000
--- a/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/layout-sw600dp/date_picker_dialog.xml b/core/res/res/layout-sw600dp/date_picker_dialog.xml
new file mode 100644
index 0000000..004d52a
--- /dev/null
+++ b/core/res/res/layout-sw600dp/date_picker_dialog.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/datePicker"
+    android:layout_gravity="center_horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    />
diff --git a/core/res/res/layout-sw600dp/number_picker.xml b/core/res/res/layout-sw600dp/number_picker.xml
new file mode 100644
index 0000000..807daf2
--- /dev/null
+++ b/core/res/res/layout-sw600dp/number_picker.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <ImageButton android:id="@+id/increment"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/numberPickerUpButtonStyle"
+        android:contentDescription="@string/number_picker_increment_button" />
+
+    <EditText android:id="@+id/numberpicker_input"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/numberPickerInputTextStyle" />
+
+    <ImageButton android:id="@+id/decrement"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/numberPickerDownButtonStyle"
+        android:contentDescription="@string/number_picker_decrement_button" />
+
+</merge>
diff --git a/core/res/res/layout/date_picker_dialog.xml b/core/res/res/layout/date_picker_dialog.xml
index 004d52a..db8f311 100644
--- a/core/res/res/layout/date_picker_dialog.xml
+++ b/core/res/res/layout/date_picker_dialog.xml
@@ -22,4 +22,6 @@
     android:layout_gravity="center_horizontal"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
+    android:spinnersShown="true"
+    android:calendarViewShown="false"
     />
diff --git a/core/res/res/layout/number_picker.xml b/core/res/res/layout/number_picker.xml
index 807daf2..f2f524c 100644
--- a/core/res/res/layout/number_picker.xml
+++ b/core/res/res/layout/number_picker.xml
@@ -23,6 +23,8 @@
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         style="?android:attr/numberPickerUpButtonStyle"
+        android:paddingTop="22dip"
+        android:paddingBottom="22dip"
         android:contentDescription="@string/number_picker_increment_button" />
 
     <EditText android:id="@+id/numberpicker_input"
@@ -34,6 +36,8 @@
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         style="?android:attr/numberPickerDownButtonStyle"
+        android:paddingTop="22dip"
+        android:paddingBottom="22dip"
         android:contentDescription="@string/number_picker_decrement_button" />
 
 </merge>
diff --git a/docs/html/sdk/android-3.1-highlights.jd b/docs/html/sdk/android-3.1-highlights.jd
index 3d132a3..88bc1ee 100644
--- a/docs/html/sdk/android-3.1-highlights.jd
+++ b/docs/html/sdk/android-3.1-highlights.jd
@@ -143,8 +143,8 @@
 <p>To make the platform even better for gaming, Android 3.1 adds support for
 most PC joysticks and gamepads that are connected over USB or Bluetooth HID.</p>
 
-<p>For example, users can connect Sony Playstation&trade; 3 and XBox 360&trade; game
-controllers over USB (but not Bluetooth), Logitech Dual Action&trade; gamepads and
+<p>For example, users can connect PlayStation<sup>&reg;</sup>3 and Xbox 360<sup>&reg;</sup>
+game controllers over USB (but not Bluetooth), Logitech Dual Action&trade; gamepads and
 flight sticks, or a car racing controller. Game controllers that use proprietary
 networking or pairing are not supported by default, but in general, the platform
 supports most PC-connectible joysticks and gamepads.</p>
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
index ffdfe66..c46d6f4 100644
--- a/libs/ui/InputTransport.cpp
+++ b/libs/ui/InputTransport.cpp
@@ -443,7 +443,8 @@
 
     if (! mPinned || ! mMotionEventSampleDataTail) {
         LOGE("channel '%s' publisher ~ Cannot append motion sample because there is no current "
-                "AMOTION_EVENT_ACTION_MOVE event.", mChannel->getName().string());
+                "AMOTION_EVENT_ACTION_MOVE or AMOTION_EVENT_ACTION_HOVER_MOVE event.",
+                mChannel->getName().string());
         return INVALID_OPERATION;
     }
 
diff --git a/location/java/android/location/Country.java b/location/java/android/location/Country.java
index 3c05403..939bd4a 100755
--- a/location/java/android/location/Country.java
+++ b/location/java/android/location/Country.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Locale;
+
 /**
  * This class wraps the country information.
  *
@@ -74,7 +76,7 @@
                 || source > COUNTRY_SOURCE_LOCALE) {
             throw new IllegalArgumentException();
         }
-        mCountryIso = countryIso.toLowerCase();
+        mCountryIso = countryIso.toUpperCase(Locale.US);
         mSource = source;
     }
 
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 6f42596..e3cbd57 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -720,8 +720,9 @@
      *
      * <p>Since API level 13, if applications set a camera via
      * {@link #setCamera(Camera)}, the apps can use the camera after this method
-     * call. The apps do not need to lock the camera again. The apps should not
-     * start another recording session during recording.
+     * call. The apps do not need to lock the camera again. However, if this
+     * method fails, the apps should still lock the camera back. The apps should
+     * not start another recording session during recording.
      *
      * @throws IllegalStateException if it is called before
      * prepare().
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index bdffce9..ed8149a 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -158,9 +158,12 @@
     mVideoSize.width  = -1;
     mVideoSize.height = -1;
 
+    int64_t token = IPCThreadState::self()->clearCallingIdentity();
     mInitCheck = init(camera, proxy, cameraId,
                     videoSize, frameRate,
                     storeMetaDataInVideoBuffers);
+    if (mInitCheck != OK) releaseCamera();
+    IPCThreadState::self()->restoreCallingIdentity(token);
 }
 
 status_t CameraSource::initCheck() const {
@@ -295,7 +298,6 @@
     if (width != -1 && height != -1) {
         if (!isVideoSizeSupported(width, height, sizes)) {
             LOGE("Video dimension (%dx%d) is unsupported", width, height);
-            releaseCamera();
             return BAD_VALUE;
         }
         if (isSetVideoSizeSupportedByCamera) {
@@ -309,7 +311,6 @@
         // If one and only one of the width and height is -1
         // we reject such a request.
         LOGE("Requested video size (%dx%d) is not supported", width, height);
-        releaseCamera();
         return BAD_VALUE;
     } else {  // width == -1 && height == -1
         // Do not configure the camera.
@@ -327,7 +328,6 @@
         if (strstr(supportedFrameRates, buf) == NULL) {
             LOGE("Requested frame rate (%d) is not supported: %s",
                 frameRate, supportedFrameRates);
-            releaseCamera();
             return BAD_VALUE;
         }
 
@@ -463,7 +463,6 @@
         bool storeMetaDataInVideoBuffers) {
 
     status_t err = OK;
-    int64_t token = IPCThreadState::self()->clearCallingIdentity();
 
     if ((err = isCameraAvailable(camera, proxy, cameraId)) != OK) {
         LOGE("Camera connection could not be established.");
@@ -505,8 +504,6 @@
         }
     }
 
-    IPCThreadState::self()->restoreCallingIdentity(token);
-
     int64_t glitchDurationUs = (1000000LL / mVideoFrameRate);
     if (glitchDurationUs > mGlitchDurationThresholdUs) {
         mGlitchDurationThresholdUs = glitchDurationUs;
@@ -573,13 +570,21 @@
 
 void CameraSource::releaseCamera() {
     LOGV("releaseCamera");
-    if ((mCameraFlags & FLAGS_HOT_CAMERA) == 0) {
-        LOGV("Camera was cold when we started, stopping preview");
-        mCamera->stopPreview();
+    if (mCamera != 0) {
+        if ((mCameraFlags & FLAGS_HOT_CAMERA) == 0) {
+            LOGV("Camera was cold when we started, stopping preview");
+            mCamera->stopPreview();
+            mCamera->disconnect();
+        } else {
+            // Unlock the camera so the application can lock it back.
+            mCamera->unlock();
+        }
+        mCamera.clear();
     }
-    mCamera.clear();
-    mCameraRecordingProxy->asBinder()->unlinkToDeath(mDeathNotifier);
-    mCameraRecordingProxy.clear();
+    if (mCameraRecordingProxy != 0) {
+        mCameraRecordingProxy->asBinder()->unlinkToDeath(mDeathNotifier);
+        mCameraRecordingProxy.clear();
+    }
     mCameraFlags = 0;
 }
 
diff --git a/nfc-extras/java/com/android/nfc_extras/NfcExecutionEnvironment.java b/nfc-extras/java/com/android/nfc_extras/NfcExecutionEnvironment.java
index eb2f6f8..63c2de2 100644
--- a/nfc-extras/java/com/android/nfc_extras/NfcExecutionEnvironment.java
+++ b/nfc-extras/java/com/android/nfc_extras/NfcExecutionEnvironment.java
@@ -55,6 +55,64 @@
      */
     public static final String EXTRA_AID = "com.android.nfc_extras.extra.AID";
 
+    /**
+     * Broadcast action: A filtered APDU was received.
+     *
+     * <p>This happens when an APDU of interest was matched by the Nfc adapter,
+     * for instance as the result of matching an externally-configured filter.
+     *
+     * <p>The filter configuration mechanism is not currently defined.
+     *
+     * <p>Always contains the extra field {@link EXTRA_APDU_BYTES}.
+     *
+     * @hide
+     */
+    public static final String ACTION_APDU_RECEIVED =
+        "com.android.nfc_extras.action.APDU_RECEIVED";
+
+    /**
+     * Mandatory byte array extra field in {@link #ACTION_APDU_RECEIVED}.
+     *
+     * <p>Contains the bytes of the received APDU.
+     *
+     * @hide
+     */
+    public static final String EXTRA_APDU_BYTES =
+        "com.android.nfc_extras.extra.APDU_BYTES";
+
+    /**
+     * Broadcast action: An EMV card removal event was detected.
+     *
+     * @hide
+     */
+    public static final String ACTION_EMV_CARD_REMOVAL =
+        "com.android.nfc_extras.action.EMV_CARD_REMOVAL";
+
+    /**
+     * Broadcast action: An adapter implementing MIFARE Classic via card
+     * emulation detected that a block has been accessed.
+     *
+     * <p>This may only be issued for the first block that the reader
+     * authenticates to.
+     *
+     * <p>May contain the extra field {@link #EXTRA_MIFARE_BLOCK}.
+     *
+     * @hide
+     */
+    public static final String ACTION_MIFARE_ACCESS_DETECTED =
+        "com.android.nfc_extras.action.MIFARE_ACCESS_DETECTED";
+
+    /**
+     * Optional integer extra field in {@link #ACTION_MIFARE_ACCESS_DETECTED}.
+     *
+     * <p>Provides the block number being accessed.  If not set, the block
+     * number being accessed is unknown.
+     *
+     * @hide
+     */
+    public static final String EXTRA_MIFARE_BLOCK =
+        "com.android.nfc_extras.extra.MIFARE_BLOCK";
+
     NfcExecutionEnvironment(NfcAdapterExtras extras) {
         mExtras = extras;
     }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index a9aa31b..9469601 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -531,7 +531,12 @@
     private void restoreFileData(String filename, BackupDataInput data) {
         byte[] bytes = new byte[data.getDataSize()];
         if (bytes.length <= 0) return;
-        restoreFileData(filename, bytes, bytes.length);
+        try {
+            data.readEntityData(bytes, 0, data.getDataSize());
+            restoreFileData(filename, bytes, bytes.length);
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to read file data for " + filename);
+        }
     }
 
     private void restoreFileData(String filename, byte[] bytes, int size) {
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 4a50d8a..b9029a7 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -569,9 +569,9 @@
         break;
     case DROP_REASON_BLOCKED:
         LOGI("Dropped event because the current application is not responding and the user "
-                "has started interating with a different application.");
+                "has started interacting with a different application.");
         reason = "inbound event was dropped because the current application is not responding "
-                "and the user has started interating with a different application";
+                "and the user has started interacting with a different application";
         break;
     case DROP_REASON_STALE:
         LOGI("Dropped event because it is stale.");
@@ -1239,37 +1239,35 @@
     const InputWindow* newHoverWindow = NULL;
 
     bool isSplit = mTouchState.split;
-    bool wrongDevice = mTouchState.down
-            && (mTouchState.deviceId != entry->deviceId
-                    || mTouchState.source != entry->source);
+    bool switchedDevice = mTouchState.deviceId != entry->deviceId
+            || mTouchState.source != entry->source;
     bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
             || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
             || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
     bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN
             || maskedAction == AMOTION_EVENT_ACTION_SCROLL
             || isHoverAction);
+    bool wrongDevice = false;
     if (newGesture) {
         bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
-        if (wrongDevice && !down) {
+        if (switchedDevice && mTouchState.down && !down) {
+#if DEBUG_FOCUS
+            LOGD("Dropping event because a pointer for a different device is already down.");
+#endif
             mTempTouchState.copyFrom(mTouchState);
-        } else {
-            mTempTouchState.reset();
-            mTempTouchState.down = down;
-            mTempTouchState.deviceId = entry->deviceId;
-            mTempTouchState.source = entry->source;
-            isSplit = false;
-            wrongDevice = false;
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            switchedDevice = false;
+            wrongDevice = true;
+            goto Failed;
         }
+        mTempTouchState.reset();
+        mTempTouchState.down = down;
+        mTempTouchState.deviceId = entry->deviceId;
+        mTempTouchState.source = entry->source;
+        isSplit = false;
     } else {
         mTempTouchState.copyFrom(mTouchState);
     }
-    if (wrongDevice) {
-#if DEBUG_FOCUS
-        LOGD("Dropping event because a pointer for a different device is already down.");
-#endif
-        injectionResult = INPUT_EVENT_INJECTION_FAILED;
-        goto Failed;
-    }
 
     if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
         /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
@@ -1598,18 +1596,38 @@
     // Update final pieces of touch state if the injector had permission.
     if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
         if (!wrongDevice) {
-            if (maskedAction == AMOTION_EVENT_ACTION_UP
-                    || maskedAction == AMOTION_EVENT_ACTION_CANCEL
-                    || isHoverAction) {
+            if (switchedDevice) {
+#if DEBUG_FOCUS
+                LOGD("Conflicting pointer actions: Switched to a different device.");
+#endif
+                *outConflictingPointerActions = true;
+            }
+
+            if (isHoverAction) {
+                // Started hovering, therefore no longer down.
+                if (mTouchState.down) {
+#if DEBUG_FOCUS
+                    LOGD("Conflicting pointer actions: Hover received while pointer was down.");
+#endif
+                    *outConflictingPointerActions = true;
+                }
+                mTouchState.reset();
+                if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
+                        || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+                    mTouchState.deviceId = entry->deviceId;
+                    mTouchState.source = entry->source;
+                }
+            } else if (maskedAction == AMOTION_EVENT_ACTION_UP
+                    || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
                 // All pointers up or canceled.
                 mTouchState.reset();
             } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
                 // First pointer went down.
                 if (mTouchState.down) {
-                    *outConflictingPointerActions = true;
 #if DEBUG_FOCUS
-                    LOGD("Pointer down received while already down.");
+                    LOGD("Conflicting pointer actions: Down received while already down.");
 #endif
+                    *outConflictingPointerActions = true;
                 }
                 mTouchState.copyFrom(mTempTouchState);
             } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
@@ -1868,6 +1886,18 @@
                 return;
             }
 
+            // If the motion event was modified in flight, then we cannot stream the sample.
+            if ((motionEventDispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_MASK)
+                    != InputTarget::FLAG_DISPATCH_AS_IS) {
+#if DEBUG_BATCHING
+                LOGD("channel '%s' ~ Not streaming because the motion event was not "
+                        "being dispatched as-is.  "
+                        "(Waiting for next dispatch cycle to start.)",
+                        connection->getInputChannelName());
+#endif
+                return;
+            }
+
             // The dispatch entry is in progress and is still potentially open for streaming.
             // Try to stream the new motion sample.  This might fail if the consumer has already
             // consumed the motion event (or if the channel is broken).
@@ -1972,6 +2002,66 @@
         dispatchEntry->headMotionSample = appendedMotionSample;
     }
 
+    // Apply target flags and update the connection's input state.
+    switch (eventEntry->type) {
+    case EventEntry::TYPE_KEY: {
+        KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+        dispatchEntry->resolvedAction = keyEntry->action;
+        dispatchEntry->resolvedFlags = keyEntry->flags;
+
+        if (!connection->inputState.trackKey(keyEntry,
+                dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+            LOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
+                    connection->getInputChannelName());
+#endif
+            return; // skip the inconsistent event
+        }
+        break;
+    }
+
+    case EventEntry::TYPE_MOTION: {
+        MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+        if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
+        } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
+        } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+        } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
+        } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
+        } else {
+            dispatchEntry->resolvedAction = motionEntry->action;
+        }
+        if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
+                && !connection->inputState.isHovering(
+                        motionEntry->deviceId, motionEntry->source)) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover enter event",
+                connection->getInputChannelName());
+#endif
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+        }
+
+        dispatchEntry->resolvedFlags = motionEntry->flags;
+        if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+            dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+        }
+
+        if (!connection->inputState.trackMotion(motionEntry,
+                dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+            LOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion event",
+                    connection->getInputChannelName());
+#endif
+            return; // skip the inconsistent event
+        }
+        break;
+    }
+    }
+
     // Enqueue the dispatch entry.
     connection->outboundQueue.enqueueAtTail(dispatchEntry);
 }
@@ -1999,16 +2089,11 @@
     case EventEntry::TYPE_KEY: {
         KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
 
-        // Apply target flags.
-        int32_t action = keyEntry->action;
-        int32_t flags = keyEntry->flags;
-
-        // Update the connection's input state.
-        connection->inputState.trackKey(keyEntry, action);
-
         // Publish the key event.
-        status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
-                action, flags, keyEntry->keyCode, keyEntry->scanCode,
+        status = connection->inputPublisher.publishKeyEvent(
+                keyEntry->deviceId, keyEntry->source,
+                dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+                keyEntry->keyCode, keyEntry->scanCode,
                 keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
                 keyEntry->eventTime);
 
@@ -2024,24 +2109,6 @@
     case EventEntry::TYPE_MOTION: {
         MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
 
-        // Apply target flags.
-        int32_t action = motionEntry->action;
-        int32_t flags = motionEntry->flags;
-        if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
-            action = AMOTION_EVENT_ACTION_OUTSIDE;
-        } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
-            action = AMOTION_EVENT_ACTION_HOVER_EXIT;
-        } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
-            action = AMOTION_EVENT_ACTION_HOVER_ENTER;
-        } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
-            action = AMOTION_EVENT_ACTION_CANCEL;
-        } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
-            action = AMOTION_EVENT_ACTION_DOWN;
-        }
-        if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
-            flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
-        }
-
         // If headMotionSample is non-NULL, then it points to the first new sample that we
         // were unable to dispatch during the previous cycle so we resume dispatching from
         // that point in the list of motion samples.
@@ -2082,13 +2149,11 @@
             }
         }
 
-        // Update the connection's input state.
-        connection->inputState.trackMotion(motionEntry, action);
-
         // Publish the motion event and the first motion sample.
-        status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
-                motionEntry->source, action, flags, motionEntry->edgeFlags,
-                motionEntry->metaState, motionEntry->buttonState,
+        status = connection->inputPublisher.publishMotionEvent(
+                motionEntry->deviceId, motionEntry->source,
+                dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+                motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState,
                 xOffset, yOffset,
                 motionEntry->xPrecision, motionEntry->yPrecision,
                 motionEntry->downTime, firstMotionSample->eventTime,
@@ -2102,8 +2167,8 @@
             return;
         }
 
-        if (action == AMOTION_EVENT_ACTION_MOVE
-                || action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+        if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_MOVE
+                || dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
             // Append additional motion samples.
             MotionSample* nextMotionSample = firstMotionSample->next;
             for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
@@ -2355,23 +2420,22 @@
                 break;
             }
 
-            int32_t xOffset, yOffset;
-            float scaleFactor;
+            InputTarget target;
             const InputWindow* window = getWindowLocked(connection->inputChannel);
             if (window) {
-                xOffset = -window->frameLeft;
-                yOffset = -window->frameTop;
-                scaleFactor = window->scaleFactor;
+                target.xOffset = -window->frameLeft;
+                target.yOffset = -window->frameTop;
+                target.scaleFactor = window->scaleFactor;
             } else {
-                xOffset = 0;
-                yOffset = 0;
-                scaleFactor = 1.0f;
+                target.xOffset = 0;
+                target.yOffset = 0;
+                target.scaleFactor = 1.0f;
             }
+            target.inputChannel = connection->inputChannel;
+            target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
 
-            DispatchEntry* cancelationDispatchEntry =
-                    mAllocator.obtainDispatchEntry(cancelationEventEntry, // increments ref
-                    0, xOffset, yOffset, scaleFactor);
-            connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
+            enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
+                    &target, false, InputTarget::FLAG_DISPATCH_AS_IS);
 
             mAllocator.releaseEventEntry(cancelationEventEntry);
         }
@@ -3327,6 +3391,7 @@
     resetTargetsLocked();
 
     mTouchState.reset();
+    mLastHoverWindow = NULL;
 }
 
 void InputDispatcher::logDispatchStateLocked() {
@@ -4125,111 +4190,180 @@
     return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
 }
 
-void InputDispatcher::InputState::trackEvent(const EventEntry* entry, int32_t action) {
-    switch (entry->type) {
-    case EventEntry::TYPE_KEY:
-        trackKey(static_cast<const KeyEntry*>(entry), action);
-        break;
+bool InputDispatcher::InputState::isHovering(int32_t deviceId, uint32_t source) const {
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
+        const MotionMemento& memento = mMotionMementos.itemAt(i);
+        if (memento.deviceId == deviceId
+                && memento.source == source
+                && memento.hovering) {
+            return true;
+        }
+    }
+    return false;
+}
 
-    case EventEntry::TYPE_MOTION:
-        trackMotion(static_cast<const MotionEntry*>(entry), action);
-        break;
+bool InputDispatcher::InputState::trackKey(const KeyEntry* entry,
+        int32_t action, int32_t flags) {
+    switch (action) {
+    case AKEY_EVENT_ACTION_UP: {
+        if (entry->flags & AKEY_EVENT_FLAG_FALLBACK) {
+            for (size_t i = 0; i < mFallbackKeys.size(); ) {
+                if (mFallbackKeys.valueAt(i) == entry->keyCode) {
+                    mFallbackKeys.removeItemsAt(i);
+                } else {
+                    i += 1;
+                }
+            }
+        }
+        ssize_t index = findKeyMemento(entry);
+        if (index >= 0) {
+            mKeyMementos.removeAt(index);
+            return true;
+        }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+        LOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, "
+                "keyCode=%d, scanCode=%d",
+                entry->deviceId, entry->source, entry->keyCode, entry->scanCode);
+#endif
+        return false;
+    }
+
+    case AKEY_EVENT_ACTION_DOWN: {
+        ssize_t index = findKeyMemento(entry);
+        if (index >= 0) {
+            mKeyMementos.removeAt(index);
+        }
+        addKeyMemento(entry, flags);
+        return true;
+    }
+
+    default:
+        return true;
     }
 }
 
-void InputDispatcher::InputState::trackKey(const KeyEntry* entry, int32_t action) {
-    if (action == AKEY_EVENT_ACTION_UP
-            && (entry->flags & AKEY_EVENT_FLAG_FALLBACK)) {
-        for (size_t i = 0; i < mFallbackKeys.size(); ) {
-            if (mFallbackKeys.valueAt(i) == entry->keyCode) {
-                mFallbackKeys.removeItemsAt(i);
-            } else {
-                i += 1;
-            }
+bool InputDispatcher::InputState::trackMotion(const MotionEntry* entry,
+        int32_t action, int32_t flags) {
+    int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+    switch (actionMasked) {
+    case AMOTION_EVENT_ACTION_UP:
+    case AMOTION_EVENT_ACTION_CANCEL: {
+        ssize_t index = findMotionMemento(entry, false /*hovering*/);
+        if (index >= 0) {
+            mMotionMementos.removeAt(index);
+            return true;
         }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+        LOGD("Dropping inconsistent motion up or cancel event: deviceId=%d, source=%08x, "
+                "actionMasked=%d",
+                entry->deviceId, entry->source, actionMasked);
+#endif
+        return false;
     }
 
+    case AMOTION_EVENT_ACTION_DOWN: {
+        ssize_t index = findMotionMemento(entry, false /*hovering*/);
+        if (index >= 0) {
+            mMotionMementos.removeAt(index);
+        }
+        addMotionMemento(entry, flags, false /*hovering*/);
+        return true;
+    }
+
+    case AMOTION_EVENT_ACTION_POINTER_UP:
+    case AMOTION_EVENT_ACTION_POINTER_DOWN:
+    case AMOTION_EVENT_ACTION_MOVE: {
+        ssize_t index = findMotionMemento(entry, false /*hovering*/);
+        if (index >= 0) {
+            MotionMemento& memento = mMotionMementos.editItemAt(index);
+            memento.setPointers(entry);
+            return true;
+        }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+        LOGD("Dropping inconsistent motion pointer up/down or move event: "
+                "deviceId=%d, source=%08x, actionMasked=%d",
+                entry->deviceId, entry->source, actionMasked);
+#endif
+        return false;
+    }
+
+    case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+        ssize_t index = findMotionMemento(entry, true /*hovering*/);
+        if (index >= 0) {
+            mMotionMementos.removeAt(index);
+            return true;
+        }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+        LOGD("Dropping inconsistent motion hover exit event: deviceId=%d, source=%08x",
+                entry->deviceId, entry->source);
+#endif
+        return false;
+    }
+
+    case AMOTION_EVENT_ACTION_HOVER_ENTER:
+    case AMOTION_EVENT_ACTION_HOVER_MOVE: {
+        ssize_t index = findMotionMemento(entry, true /*hovering*/);
+        if (index >= 0) {
+            mMotionMementos.removeAt(index);
+        }
+        addMotionMemento(entry, flags, true /*hovering*/);
+        return true;
+    }
+
+    default:
+        return true;
+    }
+}
+
+ssize_t InputDispatcher::InputState::findKeyMemento(const KeyEntry* entry) const {
     for (size_t i = 0; i < mKeyMementos.size(); i++) {
-        KeyMemento& memento = mKeyMementos.editItemAt(i);
+        const KeyMemento& memento = mKeyMementos.itemAt(i);
         if (memento.deviceId == entry->deviceId
                 && memento.source == entry->source
                 && memento.keyCode == entry->keyCode
                 && memento.scanCode == entry->scanCode) {
-            switch (action) {
-            case AKEY_EVENT_ACTION_UP:
-                mKeyMementos.removeAt(i);
-                return;
-
-            case AKEY_EVENT_ACTION_DOWN:
-                mKeyMementos.removeAt(i);
-                goto Found;
-
-            default:
-                return;
-            }
+            return i;
         }
     }
-
-Found:
-    if (action == AKEY_EVENT_ACTION_DOWN) {
-        mKeyMementos.push();
-        KeyMemento& memento = mKeyMementos.editTop();
-        memento.deviceId = entry->deviceId;
-        memento.source = entry->source;
-        memento.keyCode = entry->keyCode;
-        memento.scanCode = entry->scanCode;
-        memento.flags = entry->flags;
-        memento.downTime = entry->downTime;
-    }
+    return -1;
 }
 
-void InputDispatcher::InputState::trackMotion(const MotionEntry* entry, int32_t action) {
-    int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+ssize_t InputDispatcher::InputState::findMotionMemento(const MotionEntry* entry,
+        bool hovering) const {
     for (size_t i = 0; i < mMotionMementos.size(); i++) {
-        MotionMemento& memento = mMotionMementos.editItemAt(i);
+        const MotionMemento& memento = mMotionMementos.itemAt(i);
         if (memento.deviceId == entry->deviceId
-                && memento.source == entry->source) {
-            switch (actionMasked) {
-            case AMOTION_EVENT_ACTION_UP:
-            case AMOTION_EVENT_ACTION_CANCEL:
-            case AMOTION_EVENT_ACTION_HOVER_ENTER:
-            case AMOTION_EVENT_ACTION_HOVER_MOVE:
-            case AMOTION_EVENT_ACTION_HOVER_EXIT:
-                mMotionMementos.removeAt(i);
-                return;
-
-            case AMOTION_EVENT_ACTION_DOWN:
-                mMotionMementos.removeAt(i);
-                goto Found;
-
-            case AMOTION_EVENT_ACTION_POINTER_UP:
-            case AMOTION_EVENT_ACTION_POINTER_DOWN:
-            case AMOTION_EVENT_ACTION_MOVE:
-                memento.setPointers(entry);
-                return;
-
-            default:
-                return;
-            }
+                && memento.source == entry->source
+                && memento.hovering == hovering) {
+            return i;
         }
     }
+    return -1;
+}
 
-Found:
-    switch (actionMasked) {
-    case AMOTION_EVENT_ACTION_DOWN:
-    case AMOTION_EVENT_ACTION_HOVER_ENTER:
-    case AMOTION_EVENT_ACTION_HOVER_MOVE:
-    case AMOTION_EVENT_ACTION_HOVER_EXIT:
-        mMotionMementos.push();
-        MotionMemento& memento = mMotionMementos.editTop();
-        memento.deviceId = entry->deviceId;
-        memento.source = entry->source;
-        memento.xPrecision = entry->xPrecision;
-        memento.yPrecision = entry->yPrecision;
-        memento.downTime = entry->downTime;
-        memento.setPointers(entry);
-        memento.hovering = actionMasked != AMOTION_EVENT_ACTION_DOWN;
-    }
+void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t flags) {
+    mKeyMementos.push();
+    KeyMemento& memento = mKeyMementos.editTop();
+    memento.deviceId = entry->deviceId;
+    memento.source = entry->source;
+    memento.keyCode = entry->keyCode;
+    memento.scanCode = entry->scanCode;
+    memento.flags = flags;
+    memento.downTime = entry->downTime;
+}
+
+void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry,
+        int32_t flags, bool hovering) {
+    mMotionMementos.push();
+    MotionMemento& memento = mMotionMementos.editTop();
+    memento.deviceId = entry->deviceId;
+    memento.source = entry->source;
+    memento.flags = flags;
+    memento.xPrecision = entry->xPrecision;
+    memento.yPrecision = entry->yPrecision;
+    memento.downTime = entry->downTime;
+    memento.setPointers(entry);
+    memento.hovering = hovering;
 }
 
 void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
@@ -4243,20 +4377,17 @@
 void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime,
         Allocator* allocator, Vector<EventEntry*>& outEvents,
         const CancelationOptions& options) {
-    for (size_t i = 0; i < mKeyMementos.size(); ) {
+    for (size_t i = 0; i < mKeyMementos.size(); i++) {
         const KeyMemento& memento = mKeyMementos.itemAt(i);
         if (shouldCancelKey(memento, options)) {
             outEvents.push(allocator->obtainKeyEntry(currentTime,
                     memento.deviceId, memento.source, 0,
                     AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
                     memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
-            mKeyMementos.removeAt(i);
-        } else {
-            i += 1;
         }
     }
 
-    for (size_t i = 0; i < mMotionMementos.size(); ) {
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
         const MotionMemento& memento = mMotionMementos.itemAt(i);
         if (shouldCancelMotion(memento, options)) {
             outEvents.push(allocator->obtainMotionEntry(currentTime,
@@ -4264,12 +4395,9 @@
                     memento.hovering
                             ? AMOTION_EVENT_ACTION_HOVER_EXIT
                             : AMOTION_EVENT_ACTION_CANCEL,
-                    0, 0, 0, 0,
+                    memento.flags, 0, 0, 0,
                     memento.xPrecision, memento.yPrecision, memento.downTime,
                     memento.pointerCount, memento.pointerProperties, memento.pointerCoords));
-            mMotionMementos.removeAt(i);
-        } else {
-            i += 1;
         }
     }
 }
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 676d162..bdd1922 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -522,6 +522,10 @@
         // True if dispatch has started.
         bool inProgress;
 
+        // Set to the resolved action and flags when the event is enqueued.
+        int32_t resolvedAction;
+        int32_t resolvedFlags;
+
         // For motion events:
         //   Pointer to the first motion sample to dispatch in this cycle.
         //   Usually NULL to indicate that the list of motion samples begins at
@@ -709,14 +713,19 @@
         // Returns true if there is no state to be canceled.
         bool isNeutral() const;
 
-        // Records tracking information for an event that has just been published.
-        void trackEvent(const EventEntry* entry, int32_t action);
+        // Returns true if the specified source is known to have received a hover enter
+        // motion event.
+        bool isHovering(int32_t deviceId, uint32_t source) const;
 
         // Records tracking information for a key event that has just been published.
-        void trackKey(const KeyEntry* entry, int32_t action);
+        // Returns true if the event should be delivered, false if it is inconsistent
+        // and should be skipped.
+        bool trackKey(const KeyEntry* entry, int32_t action, int32_t flags);
 
         // Records tracking information for a motion event that has just been published.
-        void trackMotion(const MotionEntry* entry, int32_t action);
+        // Returns true if the event should be delivered, false if it is inconsistent
+        // and should be skipped.
+        bool trackMotion(const MotionEntry* entry, int32_t action, int32_t flags);
 
         // Synthesizes cancelation events for the current state and resets the tracked state.
         void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator,
@@ -756,6 +765,7 @@
         struct MotionMemento {
             int32_t deviceId;
             uint32_t source;
+            int32_t flags;
             float xPrecision;
             float yPrecision;
             nsecs_t downTime;
@@ -771,6 +781,12 @@
         Vector<MotionMemento> mMotionMementos;
         KeyedVector<int32_t, int32_t> mFallbackKeys;
 
+        ssize_t findKeyMemento(const KeyEntry* entry) const;
+        ssize_t findMotionMemento(const MotionEntry* entry, bool hovering) const;
+
+        void addKeyMemento(const KeyEntry* entry, int32_t flags);
+        void addMotionMemento(const MotionEntry* entry, int32_t flags, bool hovering);
+
         static bool shouldCancelKey(const KeyMemento& memento,
                 const CancelationOptions& options);
         static bool shouldCancelMotion(const MotionMemento& memento,
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 5a25f8c..014f962 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -53,6 +53,7 @@
 #define INDENT2 "    "
 #define INDENT3 "      "
 #define INDENT4 "        "
+#define INDENT5 "          "
 
 namespace android {
 
@@ -1921,8 +1922,17 @@
         dump.appendFormat(INDENT4 "DistanceScale: %0.3f\n", mLocked.distanceScale);
 
         dump.appendFormat(INDENT3 "Last Touch:\n");
-        dump.appendFormat(INDENT4 "Pointer Count: %d\n", mLastTouch.pointerCount);
         dump.appendFormat(INDENT4 "Button State: 0x%08x\n", mLastTouch.buttonState);
+        dump.appendFormat(INDENT4 "Pointer Count: %d\n", mLastTouch.pointerCount);
+        for (uint32_t i = 0; i < mLastTouch.pointerCount; i++) {
+            const PointerData& pointer = mLastTouch.pointers[i];
+            dump.appendFormat(INDENT5 "[%d]: id=%d, x=%d, y=%d, pressure=%d, "
+                    "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, "
+                    "orientation=%d, distance=%d, isStylus=%s\n", i,
+                    pointer.id, pointer.x, pointer.y, pointer.pressure,
+                    pointer.touchMajor, pointer.touchMinor, pointer.toolMajor, pointer.toolMinor,
+                    pointer.orientation, pointer.distance, toString(pointer.isStylus));
+        }
 
         if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
             dump.appendFormat(INDENT3 "Pointer Gesture Detector:\n");
@@ -3704,6 +3714,29 @@
                 mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
                 mPointerGesture.currentGestureIdBits, -1,
                 0, 0, mPointerGesture.downTime);
+    } else if (dispatchedGestureIdBits.isEmpty()
+            && !mPointerGesture.lastGestureIdBits.isEmpty()) {
+        // Synthesize a hover move event after all pointers go up to indicate that
+        // the pointer is hovering again even if the user is not currently touching
+        // the touch pad.  This ensures that a view will receive a fresh hover enter
+        // event after a tap.
+        float x, y;
+        mPointerController->getPosition(&x, &y);
+
+        PointerProperties pointerProperties;
+        pointerProperties.clear();
+        pointerProperties.id = 0;
+        pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
+
+        PointerCoords pointerCoords;
+        pointerCoords.clear();
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+
+        getDispatcher()->notifyMotion(when, getDeviceId(), mPointerSource, policyFlags,
+                AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+                metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+                1, &pointerProperties, &pointerCoords, 0, 0, mPointerGesture.downTime);
     }
 
     // Update state.
diff --git a/services/java/com/android/server/DnsPinger.java b/services/java/com/android/server/DnsPinger.java
index 05de53a..a7324d9 100644
--- a/services/java/com/android/server/DnsPinger.java
+++ b/services/java/com/android/server/DnsPinger.java
@@ -20,7 +20,9 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.LinkProperties;
+import android.net.NetworkUtils;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.util.Slog;
 
 import java.net.DatagramPacket;
@@ -38,7 +40,7 @@
  * API may not differentiate between a time out and a failure lookup (which we
  * really care about).
  * <p>
- * TODO : More general API.  Socket does not bind to specified connection type
+ * TODO : More general API. Socket does not bind to specified connection type
  * TODO : Choice of DNS query location - current looks up www.android.com
  *
  * @hide
@@ -56,44 +58,65 @@
     private static Random sRandom = new Random();
 
     private ConnectivityManager mConnectivityManager = null;
-    private ContentResolver mContentResolver;
     private Context mContext;
     private int mConnectionType;
+    private InetAddress mDefaultDns;
 
     private String TAG;
 
-
     /**
-     * @param connectionType The connection type from @link {@link ConnectivityManager}
+     * @param connectionType The connection type from {@link ConnectivityManager}
      */
     public DnsPinger(String TAG, Context context, int connectionType) {
         mContext = context;
-        mContentResolver = context.getContentResolver();
         mConnectionType = connectionType;
+        if (!ConnectivityManager.isNetworkTypeValid(connectionType)) {
+            Slog.e(TAG, "Invalid connectionType in constructor: " + connectionType);
+        }
         this.TAG = TAG;
+
+        mDefaultDns = getDefaultDns();
     }
 
     /**
-     * @return The first DNS in the link properties of the specified connection type
+     * @return The first DNS in the link properties of the specified connection
+     *         type or the default system DNS if the link properties has null
+     *         dns set. Should not be null.
      */
     public InetAddress getDns() {
-        LinkProperties linkProperties = getCurLinkProperties();
-        if (linkProperties == null)
-            return null;
-
-        Collection<InetAddress> dnses = linkProperties.getDnses();
-        if (dnses == null || dnses.size() == 0)
-            return null;
-
-        return dnses.iterator().next();
-    }
-
-    private LinkProperties getCurLinkProperties() {
         if (mConnectivityManager == null) {
             mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
                     Context.CONNECTIVITY_SERVICE);
         }
-        return mConnectivityManager.getLinkProperties(mConnectionType);
+
+        LinkProperties curLinkProps = mConnectivityManager.getLinkProperties(mConnectionType);
+        if (curLinkProps == null) {
+            Slog.e(TAG, "getCurLinkProperties:: LP for type" + mConnectionType + " is null!");
+            return mDefaultDns;
+        }
+
+        Collection<InetAddress> dnses = curLinkProps.getDnses();
+        if (dnses == null || dnses.size() == 0) {
+            Slog.v(TAG, "getDns::LinkProps has null dns - returning default");
+            return mDefaultDns;
+        }
+
+        return dnses.iterator().next();
+    }
+
+    private InetAddress getDefaultDns() {
+        String dns = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_DNS_SERVER);
+        if (dns == null || dns.length() == 0) {
+            dns = mContext.getResources().getString(
+                    com.android.internal.R.string.config_default_dns_server);
+        }
+        try {
+            return NetworkUtils.numericToInetAddress(dns);
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "getDefaultDns::malformed default dns address");
+            return null;
+        }
     }
 
     /**
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index b03bd4d..14abf80 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1641,7 +1641,7 @@
             final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
             if (imi == null) return false;
             final int N = subtypes.length;
-            mFileManager.addInputMethodSubtypes(mCurMethodId, subtypes);
+            mFileManager.addInputMethodSubtypes(imi, subtypes);
             buildInputMethodListLocked(mMethodList, mMethodMap);
             return true;
         }
@@ -2023,25 +2023,26 @@
                 final CharSequence label = imi.loadLabel(pm);
                 if (showSubtypes && enabledSubtypeSet.size() > 0) {
                     final int subtypeCount = imi.getSubtypeCount();
+                    if (DEBUG) {
+                        Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
+                    }
                     for (int j = 0; j < subtypeCount; ++j) {
-                        InputMethodSubtype subtype = imi.getSubtypeAt(j);
-                        if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))
-                                && !subtype.isAuxiliary()) {
+                        final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+                        final String subtypeHashCode = String.valueOf(subtype.hashCode());
+                        // We show all enabled IMEs and subtypes when an IME is shown.
+                        if (enabledSubtypeSet.contains(subtypeHashCode)
+                                && (mInputShown || !subtype.isAuxiliary())) {
                             final CharSequence title;
-                            int nameResId = subtype.getNameResId();
-                            String mode = subtype.getMode();
-                            if (nameResId != 0) {
-                                title = TextUtils.concat(subtype.getDisplayName(context,
-                                        imi.getPackageName(), imi.getServiceInfo().applicationInfo),
-                                        (TextUtils.isEmpty(label) ? "" : " (" + label + ")"));
-                            } else {
-                                CharSequence language = subtype.getLocale();
-                                // TODO: Use more friendly Title and UI
-                                title = label + "," + (mode == null ? "" : mode) + ","
-                                        + (language == null ? "" : language);
-                            }
+                            final String mode = subtype.getMode();
+                            title = TextUtils.concat(subtype.getDisplayName(context,
+                                    imi.getPackageName(), imi.getServiceInfo().applicationInfo),
+                                    (TextUtils.isEmpty(label) ? "" : " (" + label + ")"));
                             imList.add(new Pair<CharSequence, Pair<InputMethodInfo, Integer>>(
                                     title, new Pair<InputMethodInfo, Integer>(imi, j)));
+                            // Removing this subtype from enabledSubtypeSet because we no longer
+                            // need to add an entry of this subtype to imList to avoid duplicated
+                            // entries.
+                            enabledSubtypeSet.remove(subtypeHashCode);
                         }
                     }
                 } else {
@@ -2338,7 +2339,7 @@
                 }
             }
         }
-        ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
+        final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
                 applicableModeAndSubtypesMap.values());
         if (!containsKeyboardSubtype) {
             InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
@@ -3016,17 +3017,23 @@
         }
 
         public void addInputMethodSubtypes(
-                String imiId, InputMethodSubtype[] additionalSubtypes) {
+                InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
             synchronized (mMethodMap) {
+                final HashSet<InputMethodSubtype> existingSubtypes =
+                        new HashSet<InputMethodSubtype>();
+                for (int i = 0; i < imi.getSubtypeCount(); ++i) {
+                    existingSubtypes.add(imi.getSubtypeAt(i));
+                }
+
                 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
                 final int N = additionalSubtypes.length;
                 for (int i = 0; i < N; ++i) {
                     final InputMethodSubtype subtype = additionalSubtypes[i];
-                    if (!subtypes.contains(subtype)) {
+                    if (!subtypes.contains(subtype) && !existingSubtypes.contains(subtype)) {
                         subtypes.add(subtype);
                     }
                 }
-                mSubtypesMap.put(imiId, subtypes);
+                mSubtypesMap.put(imi.getId(), subtypes);
                 writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
                         mMethodMap);
             }
@@ -3133,8 +3140,8 @@
                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
                         final String imeSubtypeExtraValue =
                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
-                        final boolean isAuxiliary =
-                                Boolean.valueOf(parser.getAttributeValue(null, ATTR_IS_AUXILIARY));
+                        final boolean isAuxiliary = "1".equals(String.valueOf(
+                                parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
                         final InputMethodSubtype subtype =
                                 new InputMethodSubtype(label, icon, imeSubtypeLocale,
                                         imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index e730d0d..b31d128 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -38,6 +38,9 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
 import java.io.BufferedReader;
 import java.io.DataInputStream;
 import java.io.File;
@@ -48,6 +51,7 @@
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.NoSuchElementException;
 import java.util.StringTokenizer;
 import java.util.concurrent.CountDownLatch;
@@ -65,9 +69,18 @@
     private static final int ADD = 1;
     private static final int REMOVE = 2;
 
+    /** Path to {@code /proc/uid_stat}. */
     @Deprecated
-    private static final File STATS_UIDSTAT = new File("/proc/uid_stat");
-    private static final File STATS_NETFILTER = new File("/proc/net/xt_qtaguid/stats");
+    private final File mProcStatsUidstat;
+    /** Path to {@code /proc/net/xt_qtaguid/stats}. */
+    private final File mProcStatsNetfilter;
+
+    /** {@link #mProcStatsNetfilter} headers. */
+    private static final String KEY_IFACE = "iface";
+    private static final String KEY_TAG_HEX = "acct_tag_hex";
+    private static final String KEY_UID = "uid_tag_int";
+    private static final String KEY_RX = "rx_bytes";
+    private static final String KEY_TX = "tx_bytes";
 
     class NetdResponseCode {
         public static final int InterfaceListResult       = 110;
@@ -107,10 +120,13 @@
      *
      * @param context  Binder context for this service
      */
-    private NetworkManagementService(Context context) {
+    private NetworkManagementService(Context context, File procRoot) {
         mContext = context;
         mObservers = new ArrayList<INetworkManagementEventObserver>();
 
+        mProcStatsUidstat = new File(procRoot, "uid_stat");
+        mProcStatsNetfilter = new File(procRoot, "net/xt_qtaguid/stats");
+
         if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
             return;
         }
@@ -121,7 +137,8 @@
     }
 
     public static NetworkManagementService create(Context context) throws InterruptedException {
-        NetworkManagementService service = new NetworkManagementService(context);
+        NetworkManagementService service = new NetworkManagementService(
+                context, new File("/proc/"));
         if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
         service.mThread.start();
         if (DBG) Slog.d(TAG, "Awaiting socket connection");
@@ -130,6 +147,12 @@
         return service;
     }
 
+    // @VisibleForTesting
+    public static NetworkManagementService createForTest(Context context, File procRoot) {
+        // TODO: eventually connect with mock netd
+        return new NetworkManagementService(context, procRoot);
+    }
+
     public void registerObserver(INetworkManagementEventObserver obs) {
         Slog.d(TAG, "Registering observer");
         mObservers.add(obs);
@@ -888,7 +911,7 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
 
-        if (STATS_NETFILTER.exists()) {
+        if (mProcStatsNetfilter.exists()) {
             return getNetworkStatsDetailNetfilter(UID_ALL);
         } else {
             return getNetworkStatsDetailUidstat(UID_ALL);
@@ -902,7 +925,7 @@
                     android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
         }
 
-        if (STATS_NETFILTER.exists()) {
+        if (mProcStatsNetfilter.exists()) {
             return getNetworkStatsDetailNetfilter(uid);
         } else {
             return getNetworkStatsDetailUidstat(uid);
@@ -914,35 +937,36 @@
      */
     private NetworkStats getNetworkStatsDetailNetfilter(int limitUid) {
         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
+        final ArrayList<String> keys = Lists.newArrayList();
+        final ArrayList<String> values = Lists.newArrayList();
+        final HashMap<String, String> parsed = Maps.newHashMap();
 
         BufferedReader reader = null;
         try {
-            reader = new BufferedReader(new FileReader(STATS_NETFILTER));
+            reader = new BufferedReader(new FileReader(mProcStatsNetfilter));
 
-            // assumes format from kernel:
-            // idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
-
-            // skip first line, which is legend
+            // parse first line as header
             String line = reader.readLine();
-            while ((line = reader.readLine()) != null) {
-                final StringTokenizer t = new StringTokenizer(line);
+            splitLine(line, keys);
 
-                final String idx = t.nextToken();
-                final String iface = t.nextToken();
+            // parse remaining lines
+            while ((line = reader.readLine()) != null) {
+                splitLine(line, values);
+                parseLine(keys, values, parsed);
 
                 try {
-                    // TODO: kernel currently emits tag in upper half of long;
-                    // eventually switch to directly using int.
-                    final int tag = (int) (Long.parseLong(t.nextToken().substring(2), 16) >> 32);
-                    final int uid = Integer.parseInt(t.nextToken());
-                    final long rx = Long.parseLong(t.nextToken());
-                    final long tx = Long.parseLong(t.nextToken());
+                    final String iface = parsed.get(KEY_IFACE);
+                    final int tag = NetworkManagementSocketTagger.kernelToTag(
+                            parsed.get(KEY_TAG_HEX));
+                    final int uid = Integer.parseInt(parsed.get(KEY_UID));
+                    final long rx = Long.parseLong(parsed.get(KEY_RX));
+                    final long tx = Long.parseLong(parsed.get(KEY_TX));
 
                     if (limitUid == UID_ALL || limitUid == uid) {
                         stats.addEntry(iface, uid, tag, rx, tx);
                     }
                 } catch (NumberFormatException e) {
-                    Slog.w(TAG, "problem parsing stats for idx " + idx + ": " + e);
+                    Slog.w(TAG, "problem parsing stats row '" + line + "': " + e);
                 }
             }
         } catch (IOException e) {
@@ -964,7 +988,7 @@
     private NetworkStats getNetworkStatsDetailUidstat(int limitUid) {
         final String[] knownUids;
         if (limitUid == UID_ALL) {
-            knownUids = STATS_UIDSTAT.list();
+            knownUids = mProcStatsUidstat.list();
         } else {
             knownUids = new String[] { String.valueOf(limitUid) };
         }
@@ -973,7 +997,7 @@
                 SystemClock.elapsedRealtime(), knownUids.length);
         for (String uid : knownUids) {
             final int uidInt = Integer.parseInt(uid);
-            final File uidPath = new File(STATS_UIDSTAT, uid);
+            final File uidPath = new File(mProcStatsUidstat, uid);
             final long rx = readSingleLongFromFile(new File(uidPath, "tcp_rcv"));
             final long tx = readSingleLongFromFile(new File(uidPath, "tcp_snd"));
             stats.addEntry(IFACE_ALL, uidInt, TAG_NONE, rx, tx);
@@ -1048,6 +1072,32 @@
     }
 
     /**
+     * Split given line into {@link ArrayList}.
+     */
+    private static void splitLine(String line, ArrayList<String> outSplit) {
+        outSplit.clear();
+
+        final StringTokenizer t = new StringTokenizer(line);
+        while (t.hasMoreTokens()) {
+            outSplit.add(t.nextToken());
+        }
+    }
+
+    /**
+     * Zip the two given {@link ArrayList} as key and value pairs into
+     * {@link HashMap}.
+     */
+    private static void parseLine(
+            ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
+        outParsed.clear();
+
+        final int size = Math.min(keys.size(), values.size());
+        for (int i = 0; i < size; i++) {
+            outParsed.put(keys.get(i), values.get(i));
+        }
+    }
+
+    /**
      * Utility method to read a single plain-text {@link Long} from the given
      * {@link File}, usually from a {@code /proc/} filesystem.
      */
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index 3ba9c14..1356e2a 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -95,14 +95,14 @@
     private static final long MIN_SINGLE_DNS_CHECK_INTERVAL = 10 * 60 * 1000;
     private static final long MIN_WALLED_GARDEN_INTERVAL = 15 * 60 * 1000;
 
-    private static final int MAX_CHECKS_PER_SSID = 7;
-    private static final int NUM_DNS_PINGS = 5;
+    private static final int MAX_CHECKS_PER_SSID = 9;
+    private static final int NUM_DNS_PINGS = 7;
     private static double MIN_RESPONSE_RATE = 0.50;
 
     // TODO : Adjust multiple DNS downward to 250 on repeated failure
     // private static final int MULTI_DNS_PING_TIMEOUT_MS = 250;
 
-    private static final int DNS_PING_TIMEOUT_MS = 1000;
+    private static final int DNS_PING_TIMEOUT_MS = 800;
     private static final long DNS_PING_INTERVAL = 250;
 
     private static final long BLACKLIST_FOLLOWUP_INTERVAL = 15 * 1000;
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 584cd03..12d3ed8 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -72,6 +72,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IPowerManager;
+import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.telephony.TelephonyManager;
@@ -148,6 +149,9 @@
 
     private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS;
 
+    private static final int MSG_RULES_CHANGED = 0x1;
+    private static final int MSG_METERED_IFACES_CHANGED = 0x2;
+
     private final Context mContext;
     private final IActivityManager mActivityManager;
     private final IPowerManager mPowerManager;
@@ -210,7 +214,7 @@
 
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback);
 
         mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"));
     }
@@ -269,9 +273,6 @@
             // only someone like AMS should only be calling us
             mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
 
-            // skip when UID couldn't have any policy
-            if (!isUidValidForPolicy(mContext, uid)) return;
-
             synchronized (mRulesLock) {
                 // because a uid can have multiple pids running inside, we need to
                 // remember all pid states and summarize foreground at uid level.
@@ -292,9 +293,6 @@
             // only someone like AMS should only be calling us
             mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
 
-            // skip when UID couldn't have any policy
-            if (!isUidValidForPolicy(mContext, uid)) return;
-
             synchronized (mRulesLock) {
                 // clear records and recompute, when they exist
                 final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
@@ -599,20 +597,8 @@
             }
         }
 
-        // dispatch changed rule to existing listeners
-        // TODO: dispatch outside of holding lock
         final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]);
-        final int length = mListeners.beginBroadcast();
-        for (int i = 0; i < length; i++) {
-            final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
-            if (listener != null) {
-                try {
-                    listener.onMeteredIfacesChanged(meteredIfaces);
-                } catch (RemoteException e) {
-                }
-            }
-        }
-        mListeners.finishBroadcast();
+        mHandler.obtainMessage(MSG_METERED_IFACES_CHANGED, meteredIfaces).sendToTarget();
     }
 
     /**
@@ -804,32 +790,7 @@
 
         mListeners.register(listener);
 
-        synchronized (mRulesLock) {
-            // dispatch any existing rules to new listeners
-            // TODO: dispatch outside of holding lock
-            final int size = mUidRules.size();
-            for (int i = 0; i < size; i++) {
-                final int uid = mUidRules.keyAt(i);
-                final int uidRules = mUidRules.valueAt(i);
-                if (uidRules != RULE_ALLOW_ALL) {
-                    try {
-                        listener.onUidRulesChanged(uid, uidRules);
-                    } catch (RemoteException e) {
-                    }
-                }
-            }
-
-            // dispatch any metered ifaces to new listeners
-            // TODO: dispatch outside of holding lock
-            if (mMeteredIfaces.size() > 0) {
-                final String[] meteredIfaces = mMeteredIfaces.toArray(
-                        new String[mMeteredIfaces.size()]);
-                try {
-                    listener.onMeteredIfacesChanged(meteredIfaces);
-                } catch (RemoteException e) {
-                }
-            }
-        }
+        // TODO: consider dispatching existing rules to new listeners
     }
 
     @Override
@@ -978,8 +939,6 @@
     }
 
     private void updateRulesForUidLocked(int uid) {
-        if (!isUidValidForPolicy(mContext, uid)) return;
-
         final int uidPolicy = getUidPolicy(uid);
         final boolean uidForeground = isUidForeground(uid);
 
@@ -999,19 +958,50 @@
         //kernelSetUidRejectPaid(uid, rejectPaid);
 
         // dispatch changed rule to existing listeners
-        // TODO: dispatch outside of holding lock
-        final int length = mListeners.beginBroadcast();
-        for (int i = 0; i < length; i++) {
-            final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
-            if (listener != null) {
-                try {
-                    listener.onUidRulesChanged(uid, uidRules);
-                } catch (RemoteException e) {
+        mHandler.obtainMessage(MSG_RULES_CHANGED, uid, uidRules).sendToTarget();
+    }
+
+    private Handler.Callback mHandlerCallback = new Handler.Callback() {
+        /** {@inheritDoc} */
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_RULES_CHANGED: {
+                    final int uid = msg.arg1;
+                    final int uidRules = msg.arg2;
+                    final int length = mListeners.beginBroadcast();
+                    for (int i = 0; i < length; i++) {
+                        final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+                        if (listener != null) {
+                            try {
+                                listener.onUidRulesChanged(uid, uidRules);
+                            } catch (RemoteException e) {
+                            }
+                        }
+                    }
+                    mListeners.finishBroadcast();
+                    return true;
+                }
+                case MSG_METERED_IFACES_CHANGED: {
+                    final String[] meteredIfaces = (String[]) msg.obj;
+                    final int length = mListeners.beginBroadcast();
+                    for (int i = 0; i < length; i++) {
+                        final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+                        if (listener != null) {
+                            try {
+                                listener.onMeteredIfacesChanged(meteredIfaces);
+                            } catch (RemoteException e) {
+                            }
+                        }
+                    }
+                    mListeners.finishBroadcast();
+                    return true;
+                }
+                default: {
+                    return false;
                 }
             }
         }
-        mListeners.finishBroadcast();
-    }
+    };
 
     private String getActiveSubscriberId() {
         final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 4a79d17..7610a11 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -124,8 +124,6 @@
     private PendingIntent mPollIntent;
 
     // TODO: listen for kernel push events through netd instead of polling
-    // TODO: watch for UID uninstall, and transfer stats into single bucket
-
     // TODO: trim empty history objects entirely
 
     private static final long KB_IN_BYTES = 1024;
@@ -506,8 +504,11 @@
         try {
             networkSnapshot = mNetworkManager.getNetworkStatsSummary();
             uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
+        } catch (IllegalStateException e) {
+            Slog.w(TAG, "problem reading network stats: " + e);
+            return;
         } catch (RemoteException e) {
-            Slog.w(TAG, "problem reading network stats");
+            Slog.w(TAG, "problem reading network stats: " + e);
             return;
         }
 
diff --git a/services/tests/servicestests/res/raw/xt_qtaguid_extended b/services/tests/servicestests/res/raw/xt_qtaguid_extended
new file mode 100644
index 0000000..5bef3dd
--- /dev/null
+++ b/services/tests/servicestests/res/raw/xt_qtaguid_extended
@@ -0,0 +1,3 @@
+acct_tag_hex uid_tag_int iface rx_bytes rx_packets tx_bytes tx_packets teleported_goats
+0x0 1000 test0 1024 10 2048 20 2716057
+0x0000F00D00000000 1000 test0 512 5 512 5 3370318
diff --git a/services/tests/servicestests/res/raw/xt_qtaguid_typical b/services/tests/servicestests/res/raw/xt_qtaguid_typical
new file mode 100644
index 0000000..7c4f04e
--- /dev/null
+++ b/services/tests/servicestests/res/raw/xt_qtaguid_typical
@@ -0,0 +1,32 @@
+idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
+1 wlan0 0x0 0 14615 4270
+2 wlan0 0x0 1000 5175 915
+3 wlan0 0x0 1021 3381 903
+4 wlan0 0x0 10004 333821 53558
+5 wlan0 0x0 10010 4888 37363
+6 wlan0 0x0 10013 52 104
+7 wlan0 0x74182ada00000000 10004 18725 1066
+8 rmnet0 0x0 0 301274 30244
+9 rmnet0 0x0 1000 304 441
+10 rmnet0 0x0 1013 2880 2272
+11 rmnet0 0x0 1021 31407 8430
+12 rmnet0 0x0 10003 32665 3814
+13 rmnet0 0x0 10004 2373141 420112
+14 rmnet0 0x0 10010 870370 1111727
+15 rmnet0 0x0 10013 240 240
+16 rmnet0 0x0 10016 16703 13512
+17 rmnet0 0x0 10017 3990 3269
+18 rmnet0 0x0 10018 474504 14516062
+19 rmnet0 0x0 10019 782804 71077
+20 rmnet0 0x0 10022 70671 49684
+21 rmnet0 0x0 10029 5785354 397159
+22 rmnet0 0x0 10033 2102 1686
+23 rmnet0 0x0 10034 15495464 227694
+24 rmnet0 0x0 10037 31184994 684122
+25 rmnet0 0x0 10051 298687 113485
+26 rmnet0 0x0 10056 29504 20669
+27 rmnet0 0x0 10069 683 596
+28 rmnet0 0x0 10072 34051 12453
+29 rmnet0 0x0 10077 7025393 213866
+30 rmnet0 0x0 10081 354 1178
+31 rmnet0 0x74182ada00000000 10037 28507378 437004
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
new file mode 100644
index 0000000..ac7cb5a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.server;
+
+import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+import static com.android.server.NetworkManagementSocketTagger.tagToKernel;
+
+import android.content.res.Resources;
+import android.net.NetworkStats;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.frameworks.servicestests.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkManagementService}.
+ */
+@LargeTest
+public class NetworkManagementServiceTest extends AndroidTestCase {
+    private File mTestProc;
+    private NetworkManagementService mService;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mTestProc = getContext().getFilesDir();
+        mService = NetworkManagementService.createForTest(mContext, mTestProc);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mService = null;
+
+        super.tearDown();
+    }
+
+    public void testNetworkStatsDetail() throws Exception {
+        stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
+
+        final NetworkStats stats = mService.getNetworkStatsDetail();
+        assertEquals(31, stats.size);
+        assertStatsEntry(stats, "wlan0", 0, 0, 14615L, 4270L);
+        assertStatsEntry(stats, "wlan0", 10004, 0, 333821L, 53558L);
+        assertStatsEntry(stats, "wlan0", 10004, 1947740890, 18725L, 1066L);
+        assertStatsEntry(stats, "rmnet0", 10037, 0, 31184994L, 684122L);
+        assertStatsEntry(stats, "rmnet0", 10037, 1947740890, 28507378L, 437004L);
+    }
+
+    public void testNetworkStatsDetailExtended() throws Exception {
+        stageFile(R.raw.xt_qtaguid_extended, new File(mTestProc, "net/xt_qtaguid/stats"));
+
+        final NetworkStats stats = mService.getNetworkStatsDetail();
+        assertEquals(2, stats.size);
+        assertStatsEntry(stats, "test0", 1000, 0, 1024L, 2048L);
+        assertStatsEntry(stats, "test0", 1000, 0xF00D, 512L, 512L);
+    }
+
+    public void testKernelTags() throws Exception {
+        assertEquals("0", tagToKernel(0x0));
+        assertEquals("214748364800", tagToKernel(0x32));
+        assertEquals("9223372032559808512", tagToKernel(Integer.MAX_VALUE));
+        assertEquals("0", tagToKernel(Integer.MIN_VALUE));
+        assertEquals("9223369837831520256", tagToKernel(Integer.MIN_VALUE - 512));
+
+        assertEquals(0, kernelToTag("0x0000000000000000"));
+        assertEquals(0x32, kernelToTag("0x0000003200000000"));
+        assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
+        assertEquals(0, kernelToTag("0x0000000000000000"));
+        assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
+
+    }
+
+    /**
+     * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+     * testing purposes.
+     */
+    private void stageFile(int rawId, File file) throws Exception {
+        new File(file.getParent()).mkdirs();
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            in = getContext().getResources().openRawResource(rawId);
+            out = new FileOutputStream(file);
+            Streams.copy(in, out);
+        } finally {
+            IoUtils.closeQuietly(in);
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    private static void assertStatsEntry(
+            NetworkStats stats, String iface, int uid, int tag, long rx, long tx) {
+        final int i = stats.findIndex(iface, uid, tag);
+        assertEquals(rx, stats.rx[i]);
+        assertEquals(tx, stats.tx[i]);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 07e5425..324e896 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -41,7 +41,9 @@
 import android.app.INotificationManager;
 import android.app.IProcessObserver;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.Signature;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.INetworkPolicyListener;
@@ -63,12 +65,17 @@
 import android.util.TrustedTime;
 
 import com.android.server.net.NetworkPolicyManagerService;
+import com.google.common.util.concurrent.AbstractFuture;
 
 import org.easymock.Capture;
 import org.easymock.EasyMock;
+import org.easymock.IAnswer;
 
 import java.io.File;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Tests for {@link NetworkPolicyManagerService}.
@@ -118,11 +125,27 @@
                     public String[] getPackagesForUid(int uid) {
                         return new String[] { "com.example" };
                     }
+
+                    @Override
+                    public PackageInfo getPackageInfo(String packageName, int flags) {
+                        final PackageInfo info = new PackageInfo();
+                        final Signature signature;
+                        if ("android".equals(packageName)) {
+                            signature = new Signature("F00D");
+                        } else {
+                            signature = new Signature("DEAD");
+                        }
+                        info.signatures = new Signature[] { signature };
+                        return info;
+                    }
                 };
             }
         };
 
         mPolicyDir = getContext().getFilesDir();
+        for (File file : mPolicyDir.listFiles()) {
+            file.delete();
+        }
 
         mActivityManager = createMock(IActivityManager.class);
         mPowerManager = createMock(IPowerManager.class);
@@ -228,81 +251,110 @@
     }
 
     public void testScreenChangesRules() throws Exception {
-        // push strict policy for foreground uid, verify ALLOW rule
-        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        Future<Void> future;
+
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
         replay();
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        future.get();
+        verifyAndReset();
+
+        // push strict policy for foreground uid, verify ALLOW rule
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
         mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        future.get();
         verifyAndReset();
 
         // now turn screen off and verify REJECT rule
         expect(mPowerManager.isScreenOn()).andReturn(false).atLeastOnce();
-        expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
         replay();
         mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
+        future.get();
         verifyAndReset();
 
         // and turn screen back on, verify ALLOW rule restored
         expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
-        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
         replay();
         mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
+        future.get();
         verifyAndReset();
     }
 
     public void testPolicyNone() throws Exception {
+        Future<Void> future;
+
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        future.get();
+        verifyAndReset();
+
         // POLICY_NONE should RULE_ALLOW in foreground
-        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
         replay();
         mService.setUidPolicy(UID_A, POLICY_NONE);
-        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        future.get();
         verifyAndReset();
 
         // POLICY_NONE should RULE_ALLOW in background
-        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
         replay();
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        future.get();
         verifyAndReset();
     }
 
     public void testPolicyReject() throws Exception {
+        Future<Void> future;
+
         // POLICY_REJECT should RULE_ALLOW in background
-        expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
         replay();
         mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        future.get();
         verifyAndReset();
 
         // POLICY_REJECT should RULE_ALLOW in foreground
-        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
         replay();
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        future.get();
         verifyAndReset();
 
         // POLICY_REJECT should RULE_REJECT in background
-        expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
         replay();
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        future.get();
         verifyAndReset();
     }
 
     public void testPolicyRejectAddRemove() throws Exception {
+        Future<Void> future;
+
         // POLICY_NONE should have RULE_ALLOW in background
-        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
         replay();
-        mService.setUidPolicy(UID_A, POLICY_NONE);
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        mService.setUidPolicy(UID_A, POLICY_NONE);
+        future.get();
         verifyAndReset();
 
         // adding POLICY_REJECT should cause RULE_REJECT
-        expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
         replay();
         mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        future.get();
         verifyAndReset();
 
         // removing POLICY_REJECT should return us to RULE_ALLOW
-        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
         replay();
         mService.setUidPolicy(UID_A, POLICY_NONE);
+        future.get();
         verifyAndReset();
     }
 
@@ -350,6 +402,7 @@
         long elapsedRealtime = 0;
         NetworkState[] state = null;
         NetworkStats stats = null;
+        Future<Void> future;
 
         final long TIME_FEB_15 = 1171497600000L;
         final long TIME_MAR_10 = 1173484800000L;
@@ -360,10 +413,11 @@
         state = new NetworkState[] { buildWifi() };
         expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
         expectTime(TIME_MAR_10 + elapsedRealtime);
-        expectMeteredIfacesChanged();
+        future = expectMeteredIfacesChanged();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        future.get();
         verifyAndReset();
 
         // now change cycle to be on 15th, and test in early march, to verify we
@@ -381,26 +435,31 @@
         // TODO: write up NetworkManagementService mock
 
         expectClearNotifications();
-        expectMeteredIfacesChanged(TEST_IFACE);
+        future = expectMeteredIfacesChanged(TEST_IFACE);
 
         replay();
         setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L));
+        future.get();
         verifyAndReset();
     }
 
     public void testUidRemovedPolicyCleared() throws Exception {
+        Future<Void> future;
+
         // POLICY_REJECT should RULE_REJECT in background
-        expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
         replay();
         mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        future.get();
         verifyAndReset();
 
         // uninstall should clear RULE_REJECT
-        expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
         replay();
         final Intent intent = new Intent(ACTION_UID_REMOVED);
         intent.putExtra(EXTRA_UID, UID_A);
         mServiceContext.sendBroadcast(intent);
+        future.get();
         verifyAndReset();
     }
 
@@ -435,14 +494,35 @@
         expectLastCall().anyTimes();
     }
 
-    private void expectRulesChanged(int uid, int policy) throws Exception {
+    private Future<Void> expectRulesChanged(int uid, int policy) throws Exception {
+        final FutureAnswer future = new FutureAnswer();
         mPolicyListener.onUidRulesChanged(eq(uid), eq(policy));
-        expectLastCall().atLeastOnce();
+        expectLastCall().andAnswer(future);
+        return future;
     }
 
-    private void expectMeteredIfacesChanged(String... ifaces) throws Exception {
+    private Future<Void> expectMeteredIfacesChanged(String... ifaces) throws Exception {
+        final FutureAnswer future = new FutureAnswer();
         mPolicyListener.onMeteredIfacesChanged(aryEq(ifaces));
-        expectLastCall().atLeastOnce();
+        expectLastCall().andAnswer(future);
+        return future;
+    }
+
+    private static class FutureAnswer extends AbstractFuture<Void> implements IAnswer<Void> {
+        @Override
+        public Void get() throws InterruptedException, ExecutionException {
+            try {
+                return get(5, TimeUnit.SECONDS);
+            } catch (TimeoutException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public Void answer() {
+            set(null);
+            return null;
+        }
     }
 
     private void replay() {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 636d059..903f2b0 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -59,7 +59,6 @@
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
 import android.util.TrustedTime;
 
 import com.android.server.net.NetworkStatsService;
@@ -611,6 +610,9 @@
         mAlarmManager.setInexactRepeating(
                 eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), isA(PendingIntent.class));
         expectLastCall().atLeastOnce();
+
+        mNetManager.setBandwidthControlEnabled(true);
+        expectLastCall().atLeastOnce();
     }
 
     private void expectNetworkState(NetworkState... state) throws Exception {
@@ -631,6 +633,7 @@
 
     private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory)
             throws Exception {
+        expect(mSettings.getEnabled()).andReturn(true).anyTimes();
         expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
         expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes();
         expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes();
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index c4a6f53..6e9d0f9d 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -502,16 +502,17 @@
 
     @SmallTest
     public void testFormatNumberToE164() {
-        assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "us"));
-        assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "us"));
-        assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "us"));
+        // Note: ISO 3166-1 only allows upper case country codes.
+        assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "US"));
+        assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "US"));
+        assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "US"));
     }
 
     @SmallTest
     public void testFormatNumber() {
-        assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("650 2910000", "us"));
-        assertEquals("123-4567", PhoneNumberUtils.formatNumber("1234567", "us"));
-        assertEquals("(800) 466-4114", PhoneNumberUtils.formatNumber("800-GOOG-114", "us"));
+        assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("650 2910000", "US"));
+        assertEquals("123-4567", PhoneNumberUtils.formatNumber("1234567", "US"));
+        assertEquals("(800) 466-4114", PhoneNumberUtils.formatNumber("800-GOOG-114", "US"));
 
     }
 
diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java
index 47950e3..c031bc1 100644
--- a/voip/java/com/android/server/sip/SipHelper.java
+++ b/voip/java/com/android/server/sip/SipHelper.java
@@ -150,9 +150,17 @@
 
     private ContactHeader createContactHeader(SipProfile profile)
             throws ParseException, SipException {
-        ListeningPoint lp = getListeningPoint();
-        SipURI contactURI =
-                createSipUri(profile.getUserName(), profile.getProtocol(), lp);
+        return createContactHeader(profile, null, 0);
+    }
+
+    private ContactHeader createContactHeader(SipProfile profile,
+            String ip, int port) throws ParseException,
+            SipException {
+        SipURI contactURI = (ip == null)
+                ? createSipUri(profile.getUserName(), profile.getProtocol(),
+                        getListeningPoint())
+                : createSipUri(profile.getUserName(), profile.getProtocol(),
+                        ip, port);
 
         Address contactAddress = mAddressFactory.createAddress(contactURI);
         contactAddress.setDisplayName(profile.getDisplayName());
@@ -168,9 +176,14 @@
 
     private SipURI createSipUri(String username, String transport,
             ListeningPoint lp) throws ParseException {
-        SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress());
+        return createSipUri(username, transport, lp.getIPAddress(), lp.getPort());
+    }
+
+    private SipURI createSipUri(String username, String transport,
+            String ip, int port) throws ParseException {
+        SipURI uri = mAddressFactory.createSipURI(username, ip);
         try {
-            uri.setPort(lp.getPort());
+            uri.setPort(port);
             uri.setTransportParam(transport);
         } catch (InvalidArgumentException e) {
             throw new RuntimeException(e);
@@ -353,13 +366,14 @@
      */
     public ServerTransaction sendInviteOk(RequestEvent event,
             SipProfile localProfile, String sessionDescription,
-            ServerTransaction inviteTransaction)
-            throws SipException {
+            ServerTransaction inviteTransaction, String externalIp,
+            int externalPort) throws SipException {
         try {
             Request request = event.getRequest();
             Response response = mMessageFactory.createResponse(Response.OK,
                     request);
-            response.addHeader(createContactHeader(localProfile));
+            response.addHeader(createContactHeader(localProfile, externalIp,
+                    externalPort));
             response.setContent(sessionDescription,
                     mHeaderFactory.createContentTypeHeader(
                             "application", "sdp"));
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index c0c1b28..047eb8d 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -119,6 +119,10 @@
     private Map<String, SipSessionImpl> mSessionMap =
             new HashMap<String, SipSessionImpl>();
 
+    // external address observed from any response
+    private String mExternalIp;
+    private int mExternalPort;
+
     /**
      * @param myself the local profile with password crossed out
      * @param password the password of the profile
@@ -175,6 +179,8 @@
 
         mCallReceiverSession = null;
         mSessionMap.clear();
+
+        resetExternalAddress();
     }
 
     synchronized void onConnectivityChanged() {
@@ -190,6 +196,12 @@
         }
     }
 
+    synchronized void resetExternalAddress() {
+        Log.d(TAG, " reset external addr on " + mSipStack);
+        mExternalIp = null;
+        mExternalPort = 0;
+    }
+
     public SipProfile getLocalProfile() {
         return mLocalProfile;
     }
@@ -363,6 +375,21 @@
         return null;
     }
 
+    private void extractExternalAddress(ResponseEvent evt) {
+        Response response = evt.getResponse();
+        ViaHeader viaHeader = (ViaHeader)(response.getHeader(
+                SIPHeaderNames.VIA));
+        if (viaHeader == null) return;
+        int rport = viaHeader.getRPort();
+        String externalIp = viaHeader.getReceived();
+        if ((rport > 0) && (externalIp != null)) {
+            mExternalIp = externalIp;
+            mExternalPort = rport;
+            Log.d(TAG, " got external addr " + externalIp + ":" + rport
+                    + " on " + mSipStack);
+        }
+    }
+
     private class SipSessionCallReceiverImpl extends SipSessionImpl {
         public SipSessionCallReceiverImpl(ISipSessionListener listener) {
             super(listener);
@@ -682,6 +709,7 @@
                     dialog = ((RequestEvent) evt).getDialog();
                 } else if (evt instanceof ResponseEvent) {
                     dialog = ((ResponseEvent) evt).getDialog();
+                    extractExternalAddress((ResponseEvent) evt);
                 }
                 if (dialog != null) mDialog = dialog;
 
@@ -984,7 +1012,8 @@
                 mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
                         mLocalProfile,
                         ((MakeCallCommand) evt).getSessionDescription(),
-                        mServerTransaction);
+                        mServerTransaction,
+                        mExternalIp, mExternalPort);
                 startSessionTimer(((MakeCallCommand) evt).getTimeout());
                 return true;
             } else if (END_CALL == evt) {
@@ -1376,6 +1405,7 @@
                     if (evt instanceof ResponseEvent) {
                         if (parseOptionsResult(evt)) {
                             if (mPortChanged) {
+                                resetExternalAddress();
                                 stop();
                             } else {
                                 cancelSessionTimer();
@@ -1405,8 +1435,11 @@
                     if (!mRunning) return;
 
                     if (DEBUG_PING) {
+                        String peerUri = (mPeerProfile == null)
+                                ? "null"
+                                : mPeerProfile.getUriString();
                         Log.d(TAG, "keepalive: " + mLocalProfile.getUriString()
-                                + " --> " + mPeerProfile + ", interval=" + mInterval);
+                                + " --> " + peerUri + ", interval=" + mInterval);
                     }
                     try {
                         sendKeepAlive();