Merge "Remove redundant TODOs from PackageTracker"
diff --git a/api/current.txt b/api/current.txt
index 9c2aba7..739e7f2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37821,6 +37821,14 @@
     field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4
   }
 
+  public class MbmsStreamingManager {
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int) throws android.telephony.mbms.MbmsException;
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback) throws android.telephony.mbms.MbmsException;
+    method public void dispose();
+    method public void getStreamingServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback) throws android.telephony.mbms.MbmsException;
+  }
+
   public class NeighboringCellInfo implements android.os.Parcelable {
     ctor public deprecated NeighboringCellInfo();
     ctor public deprecated NeighboringCellInfo(int, int);
@@ -38433,6 +38441,95 @@
 
 }
 
+package android.telephony.mbms {
+
+  public class MbmsException extends java.lang.Exception {
+    method public int getErrorCode();
+    field public static final int ERROR_MIDDLEWARE_LOST = 3; // 0x3
+    field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2
+    field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public static class MbmsException.GeneralErrors {
+    ctor public MbmsException.GeneralErrors();
+    field public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; // 0xcf
+    field public static final int ERROR_IN_E911 = 204; // 0xcc
+    field public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; // 0xc9
+    field public static final int ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE = 203; // 0xcb
+    field public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 205; // 0xcd
+    field public static final int ERROR_OUT_OF_MEMORY = 202; // 0xca
+    field public static final int ERROR_UNABLE_TO_READ_SIM = 206; // 0xce
+  }
+
+  public static class MbmsException.InitializationErrors {
+    ctor public MbmsException.InitializationErrors();
+    field public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; // 0x66
+    field public static final int ERROR_DUPLICATE_INITIALIZE = 101; // 0x65
+    field public static final int ERROR_UNABLE_TO_INITIALIZE = 103; // 0x67
+  }
+
+  public static class MbmsException.StreamingErrors {
+    ctor public MbmsException.StreamingErrors();
+    field public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; // 0x12d
+    field public static final int ERROR_DUPLICATE_START_STREAM = 303; // 0x12f
+    field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
+  }
+
+  public class MbmsStreamingManagerCallback extends android.os.Binder {
+    ctor public MbmsStreamingManagerCallback();
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void middlewareReady() throws android.os.RemoteException;
+    method public void streamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>) throws android.os.RemoteException;
+  }
+
+  public class ServiceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getClassName();
+    method public java.util.List<java.util.Locale> getLocales();
+    method public java.util.Map<java.util.Locale, java.lang.String> getNames();
+    method public java.lang.String getServiceId();
+    method public java.util.Date getSessionEndTime();
+    method public java.util.Date getSessionStartTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.ServiceInfo> CREATOR;
+  }
+
+  public class StreamingService {
+    method public void dispose() throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingServiceInfo getInfo();
+    method public android.net.Uri getPlaybackUri() throws android.telephony.mbms.MbmsException;
+    method public void stopStreaming() throws android.telephony.mbms.MbmsException;
+    field public static final int BROADCAST_METHOD = 1; // 0x1
+    field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
+    field public static final int REASON_END_OF_SESSION = 2; // 0x2
+    field public static final int REASON_FREQUENCY_CONFLICT = 3; // 0x3
+    field public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 5; // 0x5
+    field public static final int REASON_NONE = 0; // 0x0
+    field public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; // 0x5
+    field public static final int REASON_OUT_OF_MEMORY = 4; // 0x4
+    field public static final int STATE_STALLED = 3; // 0x3
+    field public static final int STATE_STARTED = 2; // 0x2
+    field public static final int STATE_STOPPED = 1; // 0x1
+    field public static final int UNICAST_METHOD = 2; // 0x2
+  }
+
+  public class StreamingServiceCallback extends android.os.Binder {
+    ctor public StreamingServiceCallback();
+    method public void broadcastSignalStrengthUpdated(int) throws android.os.RemoteException;
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void mediaDescriptionUpdated() throws android.os.RemoteException;
+    method public void streamMethodUpdated(int) throws android.os.RemoteException;
+    method public void streamStateUpdated(int, int) throws android.os.RemoteException;
+    field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff
+  }
+
+  public class StreamingServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR;
+  }
+
+}
+
 package android.test {
 
   public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
diff --git a/api/system-current.txt b/api/system-current.txt
index 05b1a42..0aa5d58 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -40992,6 +40992,15 @@
     field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4
   }
 
+  public class MbmsStreamingManager {
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int) throws android.telephony.mbms.MbmsException;
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback) throws android.telephony.mbms.MbmsException;
+    method public void dispose();
+    method public void getStreamingServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback) throws android.telephony.mbms.MbmsException;
+    field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
+  }
+
   public class NeighboringCellInfo implements android.os.Parcelable {
     ctor public deprecated NeighboringCellInfo();
     ctor public deprecated NeighboringCellInfo(int, int);
@@ -41687,6 +41696,111 @@
 
 }
 
+package android.telephony.mbms {
+
+  public class MbmsException extends java.lang.Exception {
+    method public int getErrorCode();
+    field public static final int ERROR_MIDDLEWARE_LOST = 3; // 0x3
+    field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2
+    field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public static class MbmsException.GeneralErrors {
+    ctor public MbmsException.GeneralErrors();
+    field public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; // 0xcf
+    field public static final int ERROR_IN_E911 = 204; // 0xcc
+    field public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; // 0xc9
+    field public static final int ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE = 203; // 0xcb
+    field public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 205; // 0xcd
+    field public static final int ERROR_OUT_OF_MEMORY = 202; // 0xca
+    field public static final int ERROR_UNABLE_TO_READ_SIM = 206; // 0xce
+  }
+
+  public static class MbmsException.InitializationErrors {
+    ctor public MbmsException.InitializationErrors();
+    field public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; // 0x66
+    field public static final int ERROR_DUPLICATE_INITIALIZE = 101; // 0x65
+    field public static final int ERROR_UNABLE_TO_INITIALIZE = 103; // 0x67
+  }
+
+  public static class MbmsException.StreamingErrors {
+    ctor public MbmsException.StreamingErrors();
+    field public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; // 0x12d
+    field public static final int ERROR_DUPLICATE_START_STREAM = 303; // 0x12f
+    field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
+  }
+
+  public class MbmsStreamingManagerCallback extends android.os.Binder {
+    ctor public MbmsStreamingManagerCallback();
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void middlewareReady() throws android.os.RemoteException;
+    method public void streamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>) throws android.os.RemoteException;
+  }
+
+  public class ServiceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getClassName();
+    method public java.util.List<java.util.Locale> getLocales();
+    method public java.util.Map<java.util.Locale, java.lang.String> getNames();
+    method public java.lang.String getServiceId();
+    method public java.util.Date getSessionEndTime();
+    method public java.util.Date getSessionStartTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.ServiceInfo> CREATOR;
+  }
+
+  public class StreamingService {
+    method public void dispose() throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingServiceInfo getInfo();
+    method public android.net.Uri getPlaybackUri() throws android.telephony.mbms.MbmsException;
+    method public void stopStreaming() throws android.telephony.mbms.MbmsException;
+    field public static final int BROADCAST_METHOD = 1; // 0x1
+    field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
+    field public static final int REASON_END_OF_SESSION = 2; // 0x2
+    field public static final int REASON_FREQUENCY_CONFLICT = 3; // 0x3
+    field public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 5; // 0x5
+    field public static final int REASON_NONE = 0; // 0x0
+    field public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; // 0x5
+    field public static final int REASON_OUT_OF_MEMORY = 4; // 0x4
+    field public static final int STATE_STALLED = 3; // 0x3
+    field public static final int STATE_STARTED = 2; // 0x2
+    field public static final int STATE_STOPPED = 1; // 0x1
+    field public static final int UNICAST_METHOD = 2; // 0x2
+  }
+
+  public class StreamingServiceCallback extends android.os.Binder {
+    ctor public StreamingServiceCallback();
+    method public void broadcastSignalStrengthUpdated(int) throws android.os.RemoteException;
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void mediaDescriptionUpdated() throws android.os.RemoteException;
+    method public void streamMethodUpdated(int) throws android.os.RemoteException;
+    method public void streamStateUpdated(int, int) throws android.os.RemoteException;
+    field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff
+  }
+
+  public class StreamingServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+    ctor public StreamingServiceInfo(java.util.Map<java.util.Locale, java.lang.String>, java.lang.String, java.util.List<java.util.Locale>, java.lang.String, java.util.Date, java.util.Date);
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR;
+  }
+
+}
+
+package android.telephony.mbms.vendor {
+
+  public class MbmsStreamingServiceBase extends android.os.Binder {
+    ctor public MbmsStreamingServiceBase();
+    method public void dispose(int) throws android.os.RemoteException;
+    method public void disposeStream(int, java.lang.String) throws android.os.RemoteException;
+    method public android.net.Uri getPlaybackUri(int, java.lang.String) throws android.os.RemoteException;
+    method public int getStreamingServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
+    method public int initialize(android.telephony.mbms.MbmsStreamingManagerCallback, int) throws android.os.RemoteException;
+    method public int startStreaming(int, java.lang.String, android.telephony.mbms.StreamingServiceCallback) throws android.os.RemoteException;
+    method public void stopStreaming(int, java.lang.String) throws android.os.RemoteException;
+  }
+
+}
+
 package android.test {
 
   public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
diff --git a/api/test-current.txt b/api/test-current.txt
index 1d9a2d8..93c9768 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -37920,6 +37920,14 @@
     field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4
   }
 
+  public class MbmsStreamingManager {
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int) throws android.telephony.mbms.MbmsException;
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback) throws android.telephony.mbms.MbmsException;
+    method public void dispose();
+    method public void getStreamingServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback) throws android.telephony.mbms.MbmsException;
+  }
+
   public class NeighboringCellInfo implements android.os.Parcelable {
     ctor public deprecated NeighboringCellInfo();
     ctor public deprecated NeighboringCellInfo(int, int);
@@ -38532,6 +38540,95 @@
 
 }
 
+package android.telephony.mbms {
+
+  public class MbmsException extends java.lang.Exception {
+    method public int getErrorCode();
+    field public static final int ERROR_MIDDLEWARE_LOST = 3; // 0x3
+    field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2
+    field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public static class MbmsException.GeneralErrors {
+    ctor public MbmsException.GeneralErrors();
+    field public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; // 0xcf
+    field public static final int ERROR_IN_E911 = 204; // 0xcc
+    field public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; // 0xc9
+    field public static final int ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE = 203; // 0xcb
+    field public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 205; // 0xcd
+    field public static final int ERROR_OUT_OF_MEMORY = 202; // 0xca
+    field public static final int ERROR_UNABLE_TO_READ_SIM = 206; // 0xce
+  }
+
+  public static class MbmsException.InitializationErrors {
+    ctor public MbmsException.InitializationErrors();
+    field public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; // 0x66
+    field public static final int ERROR_DUPLICATE_INITIALIZE = 101; // 0x65
+    field public static final int ERROR_UNABLE_TO_INITIALIZE = 103; // 0x67
+  }
+
+  public static class MbmsException.StreamingErrors {
+    ctor public MbmsException.StreamingErrors();
+    field public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; // 0x12d
+    field public static final int ERROR_DUPLICATE_START_STREAM = 303; // 0x12f
+    field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
+  }
+
+  public class MbmsStreamingManagerCallback extends android.os.Binder {
+    ctor public MbmsStreamingManagerCallback();
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void middlewareReady() throws android.os.RemoteException;
+    method public void streamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>) throws android.os.RemoteException;
+  }
+
+  public class ServiceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getClassName();
+    method public java.util.List<java.util.Locale> getLocales();
+    method public java.util.Map<java.util.Locale, java.lang.String> getNames();
+    method public java.lang.String getServiceId();
+    method public java.util.Date getSessionEndTime();
+    method public java.util.Date getSessionStartTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.ServiceInfo> CREATOR;
+  }
+
+  public class StreamingService {
+    method public void dispose() throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingServiceInfo getInfo();
+    method public android.net.Uri getPlaybackUri() throws android.telephony.mbms.MbmsException;
+    method public void stopStreaming() throws android.telephony.mbms.MbmsException;
+    field public static final int BROADCAST_METHOD = 1; // 0x1
+    field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
+    field public static final int REASON_END_OF_SESSION = 2; // 0x2
+    field public static final int REASON_FREQUENCY_CONFLICT = 3; // 0x3
+    field public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 5; // 0x5
+    field public static final int REASON_NONE = 0; // 0x0
+    field public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; // 0x5
+    field public static final int REASON_OUT_OF_MEMORY = 4; // 0x4
+    field public static final int STATE_STALLED = 3; // 0x3
+    field public static final int STATE_STARTED = 2; // 0x2
+    field public static final int STATE_STOPPED = 1; // 0x1
+    field public static final int UNICAST_METHOD = 2; // 0x2
+  }
+
+  public class StreamingServiceCallback extends android.os.Binder {
+    ctor public StreamingServiceCallback();
+    method public void broadcastSignalStrengthUpdated(int) throws android.os.RemoteException;
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void mediaDescriptionUpdated() throws android.os.RemoteException;
+    method public void streamMethodUpdated(int) throws android.os.RemoteException;
+    method public void streamStateUpdated(int, int) throws android.os.RemoteException;
+    field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff
+  }
+
+  public class StreamingServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR;
+  }
+
+}
+
 package android.test {
 
   public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 305cf76..d2af023 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -24,6 +24,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.Objects;
+import java.util.StringJoiner;
 
 /**
  * This class represents the capabilities of a network.  This is used both to specify
@@ -347,11 +348,6 @@
         return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
     }
 
-    private boolean equalsNetCapabilitiesImmutable(NetworkCapabilities that) {
-        return ((this.mNetworkCapabilities & ~MUTABLE_CAPABILITIES) ==
-                (that.mNetworkCapabilities & ~MUTABLE_CAPABILITIES));
-    }
-
     private boolean equalsNetCapabilitiesRequestable(NetworkCapabilities that) {
         return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
                 (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES));
@@ -502,10 +498,12 @@
     private void combineTransportTypes(NetworkCapabilities nc) {
         this.mTransportTypes |= nc.mTransportTypes;
     }
+
     private boolean satisfiedByTransportTypes(NetworkCapabilities nc) {
         return ((this.mTransportTypes == 0) ||
                 ((this.mTransportTypes & nc.mTransportTypes) != 0));
     }
+
     /** @hide */
     public boolean equalsTransportTypes(NetworkCapabilities nc) {
         return (nc.mTransportTypes == this.mTransportTypes);
@@ -760,15 +758,43 @@
 
     /**
      * Checks that our immutable capabilities are the same as those of the given
-     * {@code NetworkCapabilities}.
+     * {@code NetworkCapabilities} and return a String describing any difference.
+     * The returned String is empty if there is no difference.
      *
      * @hide
      */
-    public boolean equalImmutableCapabilities(NetworkCapabilities nc) {
-        if (nc == null) return false;
-        return (equalsNetCapabilitiesImmutable(nc) &&
-                equalsTransportTypes(nc) &&
-                equalsSpecifier(nc));
+    public String describeImmutableDifferences(NetworkCapabilities that) {
+        if (that == null) {
+            return "other NetworkCapabilities was null";
+        }
+
+        StringJoiner joiner = new StringJoiner(", ");
+
+        // TODO: consider only enforcing that capabilities are not removed, allowing addition.
+        // Ignore NOT_METERED being added or removed as it is effectively dynamic. http://b/63326103
+        // TODO: properly support NOT_METERED as a mutable and requestable capability.
+        final long mask = ~MUTABLE_CAPABILITIES & ~NET_CAPABILITY_NOT_METERED;
+        long oldImmutableCapabilities = this.mNetworkCapabilities & mask;
+        long newImmutableCapabilities = that.mNetworkCapabilities & mask;
+        if (oldImmutableCapabilities != newImmutableCapabilities) {
+            String before = capabilityNamesOf(BitUtils.unpackBits(oldImmutableCapabilities));
+            String after = capabilityNamesOf(BitUtils.unpackBits(newImmutableCapabilities));
+            joiner.add(String.format("immutable capabilities changed: %s -> %s", before, after));
+        }
+
+        if (!equalsSpecifier(that)) {
+            NetworkSpecifier before = this.getNetworkSpecifier();
+            NetworkSpecifier after = that.getNetworkSpecifier();
+            joiner.add(String.format("specifier changed: %s -> %s", before, after));
+        }
+
+        if (!equalsTransportTypes(that)) {
+            String before = transportNamesOf(this.getTransportTypes());
+            String after = transportNamesOf(that.getTransportTypes());
+            joiner.add(String.format("transports changed: %s -> %s", before, after));
+        }
+
+        return joiner.toString();
     }
 
     /**
@@ -843,33 +869,15 @@
 
     @Override
     public String toString() {
+        // TODO: enumerate bits for transports and capabilities instead of creating arrays.
+        // TODO: use a StringBuilder instead of string concatenation.
         int[] types = getTransportTypes();
         String transports = (types.length > 0) ? " Transports: " + transportNamesOf(types) : "";
 
         types = getCapabilities();
         String capabilities = (types.length > 0 ? " Capabilities: " : "");
         for (int i = 0; i < types.length; ) {
-            switch (types[i]) {
-                case NET_CAPABILITY_MMS:            capabilities += "MMS"; break;
-                case NET_CAPABILITY_SUPL:           capabilities += "SUPL"; break;
-                case NET_CAPABILITY_DUN:            capabilities += "DUN"; break;
-                case NET_CAPABILITY_FOTA:           capabilities += "FOTA"; break;
-                case NET_CAPABILITY_IMS:            capabilities += "IMS"; break;
-                case NET_CAPABILITY_CBS:            capabilities += "CBS"; break;
-                case NET_CAPABILITY_WIFI_P2P:       capabilities += "WIFI_P2P"; break;
-                case NET_CAPABILITY_IA:             capabilities += "IA"; break;
-                case NET_CAPABILITY_RCS:            capabilities += "RCS"; break;
-                case NET_CAPABILITY_XCAP:           capabilities += "XCAP"; break;
-                case NET_CAPABILITY_EIMS:           capabilities += "EIMS"; break;
-                case NET_CAPABILITY_NOT_METERED:    capabilities += "NOT_METERED"; break;
-                case NET_CAPABILITY_INTERNET:       capabilities += "INTERNET"; break;
-                case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break;
-                case NET_CAPABILITY_TRUSTED:        capabilities += "TRUSTED"; break;
-                case NET_CAPABILITY_NOT_VPN:        capabilities += "NOT_VPN"; break;
-                case NET_CAPABILITY_VALIDATED:      capabilities += "VALIDATED"; break;
-                case NET_CAPABILITY_CAPTIVE_PORTAL: capabilities += "CAPTIVE_PORTAL"; break;
-                case NET_CAPABILITY_FOREGROUND:     capabilities += "FOREGROUND"; break;
-            }
+            capabilities += capabilityNameOf(types[i]);
             if (++i < types.length) capabilities += "&";
         }
 
@@ -889,15 +897,55 @@
     /**
      * @hide
      */
+    public static String capabilityNamesOf(int[] capabilities) {
+        StringJoiner joiner = new StringJoiner("|");
+        if (capabilities != null) {
+            for (int c : capabilities) {
+                joiner.add(capabilityNameOf(c));
+            }
+        }
+        return joiner.toString();
+    }
+
+    /**
+     * @hide
+     */
+    public static String capabilityNameOf(int capability) {
+        switch (capability) {
+            case NET_CAPABILITY_MMS:            return "MMS";
+            case NET_CAPABILITY_SUPL:           return "SUPL";
+            case NET_CAPABILITY_DUN:            return "DUN";
+            case NET_CAPABILITY_FOTA:           return "FOTA";
+            case NET_CAPABILITY_IMS:            return "IMS";
+            case NET_CAPABILITY_CBS:            return "CBS";
+            case NET_CAPABILITY_WIFI_P2P:       return "WIFI_P2P";
+            case NET_CAPABILITY_IA:             return "IA";
+            case NET_CAPABILITY_RCS:            return "RCS";
+            case NET_CAPABILITY_XCAP:           return "XCAP";
+            case NET_CAPABILITY_EIMS:           return "EIMS";
+            case NET_CAPABILITY_NOT_METERED:    return "NOT_METERED";
+            case NET_CAPABILITY_INTERNET:       return "INTERNET";
+            case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED";
+            case NET_CAPABILITY_TRUSTED:        return "TRUSTED";
+            case NET_CAPABILITY_NOT_VPN:        return "NOT_VPN";
+            case NET_CAPABILITY_VALIDATED:      return "VALIDATED";
+            case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
+            case NET_CAPABILITY_FOREGROUND:     return "FOREGROUND";
+            default:                            return Integer.toString(capability);
+        }
+    }
+
+    /**
+     * @hide
+     */
     public static String transportNamesOf(int[] types) {
-        if (types == null || types.length == 0) {
-            return "";
+        StringJoiner joiner = new StringJoiner("|");
+        if (types != null) {
+            for (int t : types) {
+                joiner.add(transportNameOf(t));
+            }
         }
-        StringBuilder transports = new StringBuilder();
-        for (int t : types) {
-            transports.append("|").append(transportNameOf(t));
-        }
-        return transports.substring(1);
+        return joiner.toString();
     }
 
     /**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d3d0433..8972802 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4702,10 +4702,12 @@
      */
     private void updateCapabilities(
             int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
-        if (nai.everConnected && !nai.networkCapabilities.equalImmutableCapabilities(
-                networkCapabilities)) {
-            Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: "
-                    + nai.networkCapabilities + " -> " + networkCapabilities);
+        // Sanity check: a NetworkAgent should not change its static capabilities or parameters.
+        if (nai.everConnected) {
+            String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities);
+            if (!TextUtils.isEmpty(diff)) {
+                Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities:" + diff);
+            }
         }
 
         // Don't modify caller's NetworkCapabilities.
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 4ff6657..1bf9733 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -140,6 +140,18 @@
             extraInfo = null;
         }
 
+        // Clear any previous notification with lower priority, otherwise return. http://b/63676954.
+        // A new SIGN_IN notification with a new intent should override any existing one.
+        final int previousEventId = mNotificationTypeMap.get(id);
+        final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+        if (priority(previousNotifyType) > priority(notifyType)) {
+            Slog.d(TAG, String.format(
+                    "ignoring notification %s for network %s with existing notification %s",
+                    notifyType, id, previousNotifyType));
+            return;
+        }
+        clearNotification(id);
+
         if (DBG) {
             Slog.d(TAG, String.format(
                     "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s",
@@ -270,4 +282,22 @@
         NotificationType t = NotificationType.getFromId(eventId);
         return (t != null) ? t.name() : "UNKNOWN";
     }
+
+    private static int priority(NotificationType t) {
+        if (t == null) {
+            return 0;
+        }
+        switch (t) {
+            case SIGN_IN:
+                return 4;
+            case NO_INTERNET:
+                return 3;
+            case NETWORK_SWITCH:
+                return 2;
+            case LOST_INTERNET:
+                return 1;
+            default:
+                return 0;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 1c5aa60..50f27ed 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -57,7 +57,6 @@
 import static android.app.timezone.RulesState.STAGED_OPERATION_UNINSTALL;
 import static android.app.timezone.RulesState.STAGED_OPERATION_UNKNOWN;
 
-// TODO(nfuller) Check error handling best practices in the system server.
 public final class RulesManagerService extends IRulesManager.Stub {
 
     private static final String TAG = "timezone.RulesManagerService";
@@ -336,7 +335,7 @@
         private final CheckToken mCheckToken;
         private final ICallback mCallback;
 
-        public UninstallRunnable(CheckToken checkToken, ICallback callback) {
+        UninstallRunnable(CheckToken checkToken, ICallback callback) {
             mCheckToken = checkToken;
             mCallback = callback;
         }
@@ -401,54 +400,85 @@
             if ("-format_state".equals(args[0]) && args[1] != null) {
                 for (char c : args[1].toCharArray()) {
                     switch (c) {
-                        case 'p': // Report operation in progress
-                            pw.println("Operation in progress: "
-                                    + rulesState.isOperationInProgress());
-                            break;
-                        case 's': // Report system image rules version
-                            pw.println("System rules version: "
-                                    + rulesState.getSystemRulesVersion());
-                            break;
-                        case 'c': // Report current installation state
-                            pw.println("Current install state: "
-                                    + distroStatusToString(rulesState.getDistroStatus()));
-                            break;
-                        case 'i': // Report currently installed version
-                            DistroRulesVersion installedRulesVersion =
-                                    rulesState.getInstalledDistroRulesVersion();
-                            pw.print("Installed rules version: ");
-                            if (installedRulesVersion == null) {
-                                pw.println("<None>");
-                            } else {
-                                pw.println(installedRulesVersion.toDumpString());
+                        case 'p': {
+                            // Report operation in progress
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                value = Boolean.toString(rulesState.isOperationInProgress());
                             }
+                            pw.println("Operation in progress: " + value);
                             break;
-                        case 'o': // Report staged operation type
-                            int stagedOperationType = rulesState.getStagedOperationType();
-                            pw.println("Staged operation: "
-                                    + stagedOperationToString(stagedOperationType));
+                        }
+                        case 's': {
+                            // Report system image rules version
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                value = rulesState.getSystemRulesVersion();
+                            }
+                            pw.println("System rules version: " + value);
                             break;
-                        case 't':
+                        }
+                        case 'c': {
+                            // Report current installation state
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                value = distroStatusToString(rulesState.getDistroStatus());
+                            }
+                            pw.println("Current install state: " + value);
+                            break;
+                        }
+                        case 'i': {
+                            // Report currently installed version
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                DistroRulesVersion installedRulesVersion =
+                                        rulesState.getInstalledDistroRulesVersion();
+                                if (installedRulesVersion == null) {
+                                    value = "<None>";
+                                } else {
+                                    value = installedRulesVersion.toDumpString();
+                                }
+                            }
+                            pw.println("Installed rules version: " + value);
+                            break;
+                        }
+                        case 'o': {
+                            // Report staged operation type
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                int stagedOperationType = rulesState.getStagedOperationType();
+                                value = stagedOperationToString(stagedOperationType);
+                            }
+                            pw.println("Staged operation: " + value);
+                            break;
+                        }
+                        case 't': {
                             // Report staged version (i.e. the one that will be installed next boot
                             // if the staged operation is an install).
-                            pw.print("Staged rules version: ");
-                            DistroRulesVersion stagedDistroRulesVersion =
-                                    rulesState.getStagedDistroRulesVersion();
-                            if (stagedDistroRulesVersion == null) {
-                                pw.println("<None>");
-                            } else {
-                                pw.println(stagedDistroRulesVersion.toDumpString());
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                DistroRulesVersion stagedDistroRulesVersion =
+                                        rulesState.getStagedDistroRulesVersion();
+                                if (stagedDistroRulesVersion == null) {
+                                    value = "<None>";
+                                } else {
+                                    value = stagedDistroRulesVersion.toDumpString();
+                                }
                             }
+                            pw.println("Staged rules version: " + value);
                             break;
-                        case 'a':
+                        }
+                        case 'a': {
                             // Report the active rules version (i.e. the rules in use by the current
                             // process).
                             pw.println("Active rules version (ICU, libcore): "
                                     + ICU.getTZDataVersion() + ","
                                     + ZoneInfoDB.getInstance().getVersion());
                             break;
-                        default:
+                        }
+                        default: {
                             pw.println("Unknown option: " + c);
+                        }
                     }
                 }
                 return;
diff --git a/services/core/jni/com_android_server_am_ActivityManagerService.cpp b/services/core/jni/com_android_server_am_ActivityManagerService.cpp
index 50e4502..14abaad 100644
--- a/services/core/jni/com_android_server_am_ActivityManagerService.cpp
+++ b/services/core/jni/com_android_server_am_ActivityManagerService.cpp
@@ -20,8 +20,8 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <jni.h>
 
-#include <ScopedLocalRef.h>
-#include <ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 #include <cutils/log.h>
 #include <utils/misc.h>
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 99ad00b..1a1d31e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -241,15 +241,6 @@
                 SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
             }
 
-            //
-            // Default the timezone property to GMT if not set.
-            //
-            String timezoneProperty =  SystemProperties.get("persist.sys.timezone");
-            if (timezoneProperty == null || timezoneProperty.isEmpty()) {
-                Slog.w(TAG, "Timezone not set; setting to GMT.");
-                SystemProperties.set("persist.sys.timezone", "GMT");
-            }
-
             // If the system has "persist.sys.language" and friends set, replace them with
             // "persist.sys.locale". Note that the default locale at this point is calculated
             // using the "-Duser.locale" command line flag. That flag is usually populated by
@@ -965,8 +956,13 @@
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
             }
 
-            if (!disableNonCoreServices && context.getResources().getBoolean(
-                        R.bool.config_enableUpdateableTimeZoneRules)) {
+            // timezone.RulesManagerService will prevent a device starting up if the chain of trust
+            // required for safe time zone updates might be broken. RuleManagerService cannot do
+            // this check when mOnlyCore == true, so we don't enable the service in this case.
+            final boolean startRulesManagerService =
+                    !mOnlyCore && context.getResources().getBoolean(
+                            R.bool.config_enableUpdateableTimeZoneRules);
+            if (startRulesManagerService) {
                 traceBeginAndSlog("StartTimeZoneRulesManagerService");
                 mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java
index 911f83f..d69562c 100644
--- a/telephony/java/android/telephony/MbmsStreamingManager.java
+++ b/telephony/java/android/telephony/MbmsStreamingManager.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
@@ -37,10 +39,17 @@
 
 /**
  * This class provides functionality for streaming media over MBMS.
- * @hide
  */
 public class MbmsStreamingManager {
     private static final String LOG_TAG = "MbmsStreamingManager";
+
+    /**
+     * Service action which must be handled by the middleware implementing the MBMS streaming
+     * interface.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     public static final String MBMS_STREAMING_SERVICE_ACTION =
             "android.telephony.action.EmbmsStreaming";
 
@@ -203,13 +212,23 @@
                             return;
                         } catch (RuntimeException e) {
                             Log.e(LOG_TAG, "Runtime exception during initialization");
-                            mCallbackToApp.error(
-                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
-                                    e.toString());
+                            try {
+                                mCallbackToApp.error(
+                                        MbmsException.InitializationErrors
+                                                .ERROR_UNABLE_TO_INITIALIZE,
+                                        e.toString());
+                            } catch (RemoteException e1) {
+                                // ignore
+                            }
                             return;
                         }
                         if (result != MbmsException.SUCCESS) {
-                            mCallbackToApp.error(result, "Error returned during initialization");
+                            try {
+                                mCallbackToApp.error(
+                                        result, "Error returned during initialization");
+                            } catch (RemoteException e) {
+                                // ignore
+                            }
                             return;
                         }
                         mService.set(streamingService);
diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java
index 1b87393..b8e1c49 100644
--- a/telephony/java/android/telephony/mbms/FileInfo.java
+++ b/telephony/java/android/telephony/mbms/FileInfo.java
@@ -61,6 +61,10 @@
         }
     };
 
+    /**
+     * @hide
+     * TODO: systemapi
+     */
     public FileInfo(Uri uri, String mimeType, long size, byte[] md5Hash) {
         this.uri = uri;
         this.mimeType = mimeType;
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index 6646dc8..8afe4d3 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -32,6 +32,7 @@
 public class FileServiceInfo extends ServiceInfo implements Parcelable {
     private final List<FileInfo> files;
 
+    /** @hide TODO: systemapi */
     public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
             List<Locale> newLocales, String newServiceId, Date start, Date end,
             List<FileInfo> newFiles) {
diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java
index 8888119..f51563a 100644
--- a/telephony/java/android/telephony/mbms/MbmsException.java
+++ b/telephony/java/android/telephony/mbms/MbmsException.java
@@ -16,7 +16,6 @@
 
 package android.telephony.mbms;
 
-/** @hide */
 public class MbmsException extends Exception {
     /** Indicates that the operation was successful. */
     public static final int SUCCESS = 0;
@@ -31,7 +30,7 @@
 
     /**
      * Indicates that the app attempted to perform an operation on an instance of
-     * {@link android.telephony.MbmsDownloadManager} or
+     * TODO: link android.telephony.MbmsDownloadManager or
      * {@link android.telephony.MbmsStreamingManager} without being bound to the middleware.
      */
     public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2;
@@ -47,7 +46,7 @@
         /**
          * Indicates that the app tried to create more than one instance each of
          * {@link android.telephony.MbmsStreamingManager} or
-         * {@link android.telephony.MbmsDownloadManager}.
+         * TODO: link android.telephony.MbmsDownloadManager
          */
         public static final int ERROR_DUPLICATE_INITIALIZE = 101;
         /** Indicates that the app is not authorized to access media via MBMS.*/
@@ -64,7 +63,7 @@
         /**
          * Indicates that the app attempted to perform an operation before receiving notification
          * that the middleware is ready via {@link MbmsStreamingManagerCallback#middlewareReady()}
-         * or {@link MbmsDownloadManagerCallback#middlewareReady()}.
+         * or TODO: link MbmsDownloadManagerCallback#middlewareReady
          */
         public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201;
         /**
@@ -113,6 +112,8 @@
 
     /**
      * Indicates the errors that are applicable only to the file-download use-case
+     * TODO: unhide
+     * @hide
      */
     public static class DownloadErrors {
         /**
@@ -127,9 +128,7 @@
 
     private final int mErrorCode;
 
-    /** @hide
-     * TODO: future systemapi
-     */
+    /** @hide */
     public MbmsException(int errorCode) {
         super();
         mErrorCode = errorCode;
diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
index 2e91be9..f67d6e4 100644
--- a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
@@ -16,20 +16,24 @@
 
 package android.telephony.mbms;
 
+import android.content.Context;
+import android.os.RemoteException;
+
 import java.util.List;
 
 /**
- * A Parcelable class with Cell-Broadcast service information.
- * @hide
+ * A callback class that is used to receive information from the middleware on MBMS streaming
+ * services. An instance of this object should be passed into
+ * {@link android.telephony.MbmsStreamingManager#create(Context, MbmsStreamingManagerCallback)}.
  */
 public class MbmsStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
-
-    public final static int ERROR_CARRIER_NOT_SUPPORTED      = 1;
-    public final static int ERROR_UNABLE_TO_INITIALIZE       = 2;
-    public final static int ERROR_UNABLE_TO_ALLOCATE_MEMORY  = 3;
-
-
-    public void error(int errorCode, String message) {
+    /**
+     * Called by the middleware when it has detected an error condition. The possible error codes
+     * are listed in {@link MbmsException}.
+     * @param errorCode The error code.
+     * @param message A human-readable message generated by the middleware for debugging purposes.
+     */
+    public void error(int errorCode, String message) throws RemoteException {
         // default implementation empty
     }
 
@@ -45,7 +49,8 @@
      * @param services a List of StreamingServiceInfos
      *
      */
-    public void streamingServicesUpdated(List<StreamingServiceInfo> services) {
+    public void streamingServicesUpdated(List<StreamingServiceInfo> services)
+            throws RemoteException {
         // default implementation empty
     }
 
@@ -58,7 +63,7 @@
      * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
      */
     @Override
-    public void middlewareReady() {
+    public void middlewareReady() throws RemoteException {
         // default implementation empty
     }
 }
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index f9ad44c..e1ccd43 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -30,43 +30,21 @@
 import java.util.Set;
 
 /**
- * A Parcelable class with Cell-Broadcast service information.
- * @hide
+ * Describes a cell-broadcast service. This class should not be instantiated directly -- use
+ * {@link StreamingServiceInfo} or FileServiceInfo TODO: add link once that's unhidden
  */
 public class ServiceInfo implements Parcelable {
     // arbitrary limit on the number of locale -> name pairs we support
     final static int MAP_LIMIT = 1000;
-    /**
-     * User displayable names listed by language.  Unmodifiable.
-     */
-    final Map<Locale, String> names;
 
-    /**
-     * The class name for this service - used to catagorize and filter
-     */
-    final String className;
+    private final Map<Locale, String> names;
+    private final String className;
+    private final List<Locale> locales;
+    private final String serviceId;
+    private final Date sessionStartTime;
+    private final Date sessionEndTime;
 
-    /**
-     * The languages available for this service content
-     */
-    final List<Locale> locales;
-
-    /**
-     * The carrier's identifier for the service.
-     */
-    final String serviceId;
-
-    /**
-     * The start time indicating when this service will be available.
-     */
-    final Date sessionStartTime;
-
-    /**
-     * The end time indicating when this sesion stops being available.
-     */
-    final Date sessionEndTime;
-
-
+    /** @hide */
     public ServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales,
             String newServiceId, Date start, Date end) {
         if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName)
@@ -89,20 +67,21 @@
         sessionEndTime = (Date)end.clone();
     }
 
-    public static final Parcelable.Creator<FileServiceInfo> CREATOR =
-            new Parcelable.Creator<FileServiceInfo>() {
+    public static final Parcelable.Creator<ServiceInfo> CREATOR =
+            new Parcelable.Creator<ServiceInfo>() {
         @Override
-        public FileServiceInfo createFromParcel(Parcel source) {
-            return new FileServiceInfo(source);
+        public ServiceInfo createFromParcel(Parcel source) {
+            return new ServiceInfo(source);
         }
 
         @Override
-        public FileServiceInfo[] newArray(int size) {
-            return new FileServiceInfo[size];
+        public ServiceInfo[] newArray(int size) {
+            return new ServiceInfo[size];
         }
     };
 
-    ServiceInfo(Parcel in) {
+    /** @hide */
+    protected ServiceInfo(Parcel in) {
         int mapCount = in.readInt();
         if (mapCount > MAP_LIMIT || mapCount < 0) {
             throw new RuntimeException("bad map length" + mapCount);
@@ -152,26 +131,44 @@
         return 0;
     }
 
+    /**
+     * User displayable names listed by language. Do not modify the map returned from this method.
+     */
     public Map<Locale, String> getNames() {
         return names;
     }
 
+    /**
+     * The class name for this service - used to categorize and filter
+     */
     public String getClassName() {
         return className;
     }
 
+    /**
+     * The languages available for this service content
+     */
     public List<Locale> getLocales() {
         return locales;
     }
 
+    /**
+     * The carrier's identifier for the service.
+     */
     public String getServiceId() {
         return serviceId;
     }
 
+    /**
+     * The start time indicating when this service will be available.
+     */
     public Date getSessionStartTime() {
         return sessionStartTime;
     }
 
+    /**
+     * The end time indicating when this session stops being available.
+     */
     public Date getSessionEndTime() {
         return sessionEndTime;
     }
diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java
index c49f8a9..42c78c3 100644
--- a/telephony/java/android/telephony/mbms/StreamingService.java
+++ b/telephony/java/android/telephony/mbms/StreamingService.java
@@ -26,7 +26,10 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * @hide
+ * Class used to represent a single MBMS stream. After a stream has been started with
+ * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+ * StreamingServiceCallback)},
+ * this class is used to hold information about the stream and control it.
  */
 public class StreamingService {
     private static final String LOG_TAG = "MbmsStreamingService";
@@ -60,7 +63,8 @@
 
     /**
      * State changed due to a call to {@link #stopStreaming()} or
-     * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo, StreamingServiceCallback)}
+     * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+     * StreamingServiceCallback)}
      */
     public static final int REASON_BY_USER_REQUEST = 1;
 
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
index cab9c23..6a1ff9c 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
@@ -16,9 +16,11 @@
 
 package android.telephony.mbms;
 
+import android.os.RemoteException;
+
 /**
- * A Callback class for use when the application is actively streaming content.
- * @hide
+ * A callback class for use when the application is actively streaming content. The middleware
+ * will provide updates on the status of the stream via this callback.
  */
 public class StreamingServiceCallback extends IStreamingServiceCallback.Stub {
 
@@ -31,8 +33,14 @@
      */
     public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1;
 
+    /**
+     * Called by the middleware when it has detected an error condition in this stream. The
+     * possible error codes are listed in {@link MbmsException}.
+     * @param errorCode The error code.
+     * @param message A human-readable message generated by the middleware for debugging purposes.
+     */
     @Override
-    public void error(int errorCode, String message) {
+    public void error(int errorCode, String message) throws RemoteException {
         // default implementation empty
     }
 
@@ -44,7 +52,7 @@
      */
     @Override
     public void streamStateUpdated(@StreamingService.StreamingState int state,
-            @StreamingService.StreamingStateChangeReason int reason) {
+            @StreamingService.StreamingStateChangeReason int reason) throws RemoteException {
         // default implementation empty
     }
 
@@ -59,7 +67,7 @@
      * when parameters have changed to account for time drift.
      */
     @Override
-    public void mediaDescriptionUpdated() {
+    public void mediaDescriptionUpdated() throws RemoteException {
         // default implementation empty
     }
 
@@ -74,7 +82,7 @@
      * for this service due to timing, geography or popularity.
      */
     @Override
-    public void broadcastSignalStrengthUpdated(int signalStrength) {
+    public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
         // default implementation empty
     }
 
@@ -95,7 +103,7 @@
      * {@link StreamingService#UNICAST_METHOD}
      */
     @Override
-    public void streamMethodUpdated(int methodType) {
+    public void streamMethodUpdated(int methodType) throws RemoteException {
         // default implementation empty
     }
 }
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
index 77ce3bb..58df24d 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
@@ -16,6 +16,7 @@
 
 package android.telephony.mbms;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -25,15 +26,24 @@
 import java.util.Map;
 
 /**
- * A Parcelable class Cell-Broadcast media stream information.
- * This may not have any more info than ServiceInfo, but kept for completeness.
- * @hide
+ * Describes a single MBMS streaming service.
  */
 public class StreamingServiceInfo extends ServiceInfo implements Parcelable {
 
-    public StreamingServiceInfo(Map<Locale, String> newNames, String newClassName,
-            List<Locale> newLocales, String newServiceId, Date start, Date end) {
-        super(newNames, newClassName, newLocales, newServiceId, start, end);
+    /**
+     * @param names User displayable names listed by language.
+     * @param className The class name for this service - used by frontend apps to categorize and
+     *                  filter.
+     * @param locales The languages available for this service content.
+     * @param serviceId The carrier's identifier for the service.
+     * @param start The start time indicating when this service will be available.
+     * @param end The end time indicating when this session stops being available.
+     * @hide
+     */
+    @SystemApi
+    public StreamingServiceInfo(Map<Locale, String> names, String className,
+            List<Locale> locales, String serviceId, Date start, Date end) {
+        super(names, className, locales, serviceId, start, end);
     }
 
     public static final Parcelable.Creator<StreamingServiceInfo> CREATOR =
@@ -49,7 +59,7 @@
         }
     };
 
-    StreamingServiceInfo(Parcel in) {
+    private StreamingServiceInfo(Parcel in) {
         super(in);
     }
 
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 585d5b9..b2200c3 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -17,18 +17,23 @@
 package android.telephony.mbms.vendor;
 
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.telephony.mbms.IMbmsStreamingManagerCallback;
 import android.telephony.mbms.IStreamingServiceCallback;
 import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.StreamingService;
+import android.telephony.mbms.StreamingServiceCallback;
+import android.telephony.mbms.StreamingServiceInfo;
 
 import java.util.List;
 
 /**
  * @hide
- * TODO: future systemapi
  */
+@SystemApi
 public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
     /**
      * Initialize streaming service for this app and subId, registering the listener.
@@ -44,13 +49,39 @@
      * @param listener The callback to use to communicate with the app.
      * @param subscriptionId The subscription ID to use.
      */
-    @Override
-    public int initialize(IMbmsStreamingManagerCallback listener, int subscriptionId)
+    public int initialize(MbmsStreamingManagerCallback listener, int subscriptionId)
             throws RemoteException {
         return 0;
     }
 
     /**
+     * Actual AIDL implementation that hides the callback AIDL from the middleware.
+     * @hide
+     */
+    @Override
+    public final int initialize(IMbmsStreamingManagerCallback listener, int subscriptionId)
+            throws RemoteException {
+        return initialize(new MbmsStreamingManagerCallback() {
+            @Override
+            public void error(int errorCode, String message) throws RemoteException {
+                listener.error(errorCode, message);
+            }
+
+            @Override
+            public void streamingServicesUpdated(List<StreamingServiceInfo> services) throws
+                    RemoteException {
+                listener.streamingServicesUpdated(services);
+            }
+
+            @Override
+            public void middlewareReady() throws RemoteException {
+                listener.middlewareReady();
+            }
+        }, subscriptionId);
+    }
+
+
+    /**
      * Registers serviceClasses of interest with the appName/subId key.
      * Starts async fetching data on streaming services of matching classes to be reported
      * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)}
@@ -85,10 +116,47 @@
      * @param listener The listener object on which the app wishes to receive updates.
      * @return Any error in {@link android.telephony.mbms.MbmsException.GeneralErrors}
      */
+    public int startStreaming(int subscriptionId, String serviceId,
+            StreamingServiceCallback listener) throws RemoteException {
+        return 0;
+    }
+
+    /**
+     * Actual AIDL implementation of startStreaming that hides the callback AIDL from the
+     * middleware.
+     * @hide
+     */
     @Override
     public int startStreaming(int subscriptionId, String serviceId,
             IStreamingServiceCallback listener) throws RemoteException {
-        return 0;
+        return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
+            @Override
+            public void error(int errorCode, String message) throws RemoteException {
+                listener.error(errorCode, message);
+            }
+
+            @Override
+            public void streamStateUpdated(@StreamingService.StreamingState int state,
+                    @StreamingService.StreamingStateChangeReason int reason)
+                    throws RemoteException {
+                listener.streamStateUpdated(state, reason);
+            }
+
+            @Override
+            public void mediaDescriptionUpdated() throws RemoteException {
+                listener.mediaDescriptionUpdated();
+            }
+
+            @Override
+            public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
+                listener.broadcastSignalStrengthUpdated(signalStrength);
+            }
+
+            @Override
+            public void streamMethodUpdated(int methodType) throws RemoteException {
+                listener.streamMethodUpdated(methodType);
+            }
+        });
     }
 
     /**
diff --git a/tests/CoreTests/android/core/HeapTest.java b/tests/CoreTests/android/core/HeapTest.java
deleted file mode 100644
index 400d041..0000000
--- a/tests/CoreTests/android/core/HeapTest.java
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package android.core;
-
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-import android.test.suitebuilder.annotation.Suppress;
-import dalvik.system.VMRuntime;
-import junit.framework.TestCase;
-
-import java.lang.ref.PhantomReference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.SoftReference;
-import java.lang.ref.WeakReference;
-import java.util.LinkedList;
-import java.util.Random;
-
-
-public class HeapTest extends TestCase {
-
-    private static final String TAG = "HeapTest";
-
-    /**
-     * Returns a WeakReference to an object that has no
-     * other references.  This is done in a separate method
-     * to ensure that the Object's address isn't sitting in
-     * a stale local register.
-     */
-    private WeakReference<Object> newRef() {
-        return new WeakReference<Object>(new Object());
-    }
-
-    private static void makeRefs(Object objects[], SoftReference<Object> refs[]) {
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = (Object) new byte[8 * 1024];
-            refs[i] = new SoftReference<Object>(objects[i]);
-        }
-    }
-
-    private static <T> int checkRefs(SoftReference<T> refs[], int last) {
-        int i;
-        int numCleared = 0;
-        for (i = 0; i < refs.length; i++) {
-            Object o = refs[i].get();
-            if (o == null) {
-                numCleared++;
-            }
-        }
-        if (numCleared != last) {
-            Log.i(TAG, "****** " + numCleared + "/" + i + " cleared ******");
-        }
-        return numCleared;
-    }
-
-    private static void clearRefs(Object objects[], int skip) {
-        for (int i = 0; i < objects.length; i += skip) {
-            objects[i] = null;
-        }
-    }
-
-    private static void clearRefs(Object objects[]) {
-        clearRefs(objects, 1);
-    }
-
-    private static <T> void checkRefs(T objects[], SoftReference<T> refs[]) {
-        boolean ok = true;
-
-        for (int i = 0; i < objects.length; i++) {
-            if (refs[i].get() != objects[i]) {
-                ok = false;
-            }
-        }
-        if (!ok) {
-            throw new RuntimeException("Test failed: soft refs not cleared");
-        }
-    }
-
-    @MediumTest
-    public void testGcSoftRefs() throws Exception {
-        final int NUM_REFS = 128;
-
-        Object objects[] = new Object[NUM_REFS];
-        SoftReference<Object> refs[] = new SoftReference[objects.length];
-
-        /* Create a bunch of objects and a parallel array
-         * of SoftReferences.
-         */
-        makeRefs(objects, refs);
-        Runtime.getRuntime().gc();
-
-        /* Let go of some of the hard references to the objects so that
-         * the references can be cleared.
-         */
-        clearRefs(objects, 3);
-
-        /* Collect all softly-reachable objects.
-         */
-        VMRuntime.getRuntime().gcSoftReferences();
-        Runtime.getRuntime().runFinalization();
-
-        /* Make sure that the objects were collected.
-         */
-        checkRefs(objects, refs);
-
-        /* Remove more hard references and re-check.
-         */
-        clearRefs(objects, 2);
-        VMRuntime.getRuntime().gcSoftReferences();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs);
-
-        /* Remove the rest of the references and re-check.
-         */
-        /* Remove more hard references and re-check.
-         */
-        clearRefs(objects);
-        VMRuntime.getRuntime().gcSoftReferences();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs);
-    }
-
-    public void xxtestSoftRefPartialClean() throws Exception {
-        final int NUM_REFS = 128;
-
-        Object objects[] = new Object[NUM_REFS];
-        SoftReference<Object> refs[] = new SoftReference[objects.length];
-
-        /* Create a bunch of objects and a parallel array
-        * of SoftReferences.
-        */
-        makeRefs(objects, refs);
-        Runtime.getRuntime().gc();
-
-        /* Let go of the hard references to the objects so that
-        * the references can be cleared.
-        */
-        clearRefs(objects);
-
-        /* Start creating a bunch of temporary and permanent objects
-        * to drive GC.
-        */
-        final int NUM_OBJECTS = 64 * 1024;
-        Object junk[] = new Object[NUM_OBJECTS];
-        Random random = new Random();
-
-        int i = 0;
-        int mod = 0;
-        int totalSize = 0;
-        int cleared = -1;
-        while (i < junk.length && totalSize < 8 * 1024 * 1024) {
-            int r = random.nextInt(64 * 1024) + 128;
-            Object o = (Object) new byte[r];
-            if (++mod % 16 == 0) {
-                junk[i++] = o;
-                totalSize += r * 4;
-            }
-            cleared = checkRefs(refs, cleared);
-        }
-    }
-
-    private static void makeRefs(Object objects[], WeakReference<Object> refs[]) {
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = new Object();
-            refs[i] = new WeakReference<Object>(objects[i]);
-        }
-    }
-
-    private static <T> void checkRefs(T objects[], WeakReference<T> refs[]) {
-        boolean ok = true;
-
-        for (int i = 0; i < objects.length; i++) {
-            if (refs[i].get() != objects[i]) {
-                ok = false;
-            }
-        }
-        if (!ok) {
-            throw new RuntimeException("Test failed: " +
-                    "weak refs not cleared");
-        }
-    }
-
-    @MediumTest
-    public void testWeakRefs() throws Exception {
-        final int NUM_REFS = 16;
-
-        Object objects[] = new Object[NUM_REFS];
-        WeakReference<Object> refs[] = new WeakReference[objects.length];
-
-        /* Create a bunch of objects and a parallel array
-        * of WeakReferences.
-        */
-        makeRefs(objects, refs);
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs);
-
-        /* Clear out every other strong reference.
-        */
-        for (int i = 0; i < objects.length; i += 2) {
-            objects[i] = null;
-        }
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs);
-
-        /* Clear out the rest of them.
-        */
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = null;
-        }
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs);
-    }
-
-    private static void makeRefs(Object objects[], PhantomReference<Object> refs[],
-            ReferenceQueue<Object> queue) {
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = new Object();
-            refs[i] = new PhantomReference<Object>(objects[i], queue);
-        }
-    }
-
-    static <T> void checkRefs(T objects[], PhantomReference<T> refs[],
-            ReferenceQueue<T> queue) {
-        boolean ok = true;
-
-        /* Make sure that the reference that should be on
-        * the queue are marked as enqueued.  Once we
-        * pull them off the queue, they will no longer
-        * be marked as enqueued.
-        */
-        for (int i = 0; i < objects.length; i++) {
-            if (objects[i] == null && refs[i] != null) {
-                if (!refs[i].isEnqueued()) {
-                    ok = false;
-                }
-            }
-        }
-        if (!ok) {
-            throw new RuntimeException("Test failed: " +
-                    "phantom refs not marked as enqueued");
-        }
-
-        /* Make sure that all of the references on the queue
-        * are supposed to be there.
-        */
-        PhantomReference<T> ref;
-        while ((ref = (PhantomReference<T>) queue.poll()) != null) {
-            /* Find the list index that corresponds to this reference.
-            */
-            int i;
-            for (i = 0; i < objects.length; i++) {
-                if (refs[i] == ref) {
-                    break;
-                }
-            }
-            if (i == objects.length) {
-                throw new RuntimeException("Test failed: " +
-                        "unexpected ref on queue");
-            }
-            if (objects[i] != null) {
-                throw new RuntimeException("Test failed: " +
-                        "reference enqueued for strongly-reachable " +
-                        "object");
-            }
-            refs[i] = null;
-
-            /* TODO: clear doesn't do much, since we're losing the
-            * strong ref to the ref object anyway.  move the ref
-            * into another list.
-            */
-            ref.clear();
-        }
-
-        /* We've visited all of the enqueued references.
-        * Make sure that there aren't any other references
-        * that should have been enqueued.
-        *
-        * NOTE: there is a race condition here;  this assumes
-        * that the VM has serviced all outstanding reference
-        * enqueue() calls.
-        */
-        for (int i = 0; i < objects.length; i++) {
-            if (objects[i] == null && refs[i] != null) {
-//                System.out.println("HeapTest/PhantomRefs: refs[" + i +
-//                        "] should be enqueued");
-                ok = false;
-            }
-        }
-        if (!ok) {
-            throw new RuntimeException("Test failed: " +
-                    "phantom refs not enqueued");
-        }
-    }
-
-    @MediumTest
-    public void testPhantomRefs() throws Exception {
-        final int NUM_REFS = 16;
-
-        Object objects[] = new Object[NUM_REFS];
-        PhantomReference<Object> refs[] = new PhantomReference[objects.length];
-        ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
-
-        /* Create a bunch of objects and a parallel array
-        * of PhantomReferences.
-        */
-        makeRefs(objects, refs, queue);
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs, queue);
-
-        /* Clear out every other strong reference.
-        */
-        for (int i = 0; i < objects.length; i += 2) {
-            objects[i] = null;
-        }
-        // System.out.println("HeapTest/PhantomRefs: cleared evens");
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs, queue);
-
-        /* Clear out the rest of them.
-        */
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = null;
-        }
-        // System.out.println("HeapTest/PhantomRefs: cleared all");
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs, queue);
-    }
-
-    private static int sNumFinalized = 0;
-    private static final Object sLock = new Object();
-
-    private static class FinalizableObject {
-        protected void finalize() {
-            // System.out.println("gc from finalize()");
-            Runtime.getRuntime().gc();
-            synchronized (sLock) {
-                sNumFinalized++;
-            }
-        }
-    }
-
-    private static void makeRefs(FinalizableObject objects[],
-            WeakReference<FinalizableObject> refs[]) {
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = new FinalizableObject();
-            refs[i] = new WeakReference<FinalizableObject>(objects[i]);
-        }
-    }
-
-    @LargeTest
-    public void testWeakRefsAndFinalizers() throws Exception {
-        final int NUM_REFS = 16;
-
-        FinalizableObject objects[] = new FinalizableObject[NUM_REFS];
-        WeakReference<FinalizableObject> refs[] = new WeakReference[objects.length];
-        int numCleared;
-
-        /* Create a bunch of objects and a parallel array
-        * of WeakReferences.
-        */
-        makeRefs(objects, refs);
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs);
-
-        /* Clear out every other strong reference.
-        */
-        sNumFinalized = 0;
-        numCleared = 0;
-        for (int i = 0; i < objects.length; i += 2) {
-            objects[i] = null;
-            numCleared++;
-        }
-        // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared evens");
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs);
-        if (sNumFinalized != numCleared) {
-            throw new RuntimeException("Test failed: " +
-                    "expected " + numCleared + " finalizations, saw " +
-                    sNumFinalized);
-        }
-
-        /* Clear out the rest of them.
-        */
-        sNumFinalized = 0;
-        numCleared = 0;
-        for (int i = 0; i < objects.length; i++) {
-            if (objects[i] != null) {
-                objects[i] = null;
-                numCleared++;
-            }
-        }
-        // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared all");
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs);
-        if (sNumFinalized != numCleared) {
-            throw new RuntimeException("Test failed: " +
-                    "expected " + numCleared + " finalizations, saw " +
-                    sNumFinalized);
-        }
-    }
-
-    // TODO: flaky test
-    //@MediumTest
-    public void testOomeLarge() throws Exception {
-        /* Just shy of the typical max heap size so that it will actually
-         * try to allocate it instead of short-circuiting.
-         */
-        final int SIXTEEN_MB = (16 * 1024 * 1024 - 32);
-
-        Boolean sawEx = false;
-        byte a[];
-
-        try {
-            a = new byte[SIXTEEN_MB];
-        } catch (OutOfMemoryError oom) {
-            //Log.i(TAG, "HeapTest/OomeLarge caught " + oom);
-            sawEx = true;
-        }
-
-        if (!sawEx) {
-            throw new RuntimeException("Test failed: " +
-                    "OutOfMemoryError not thrown");
-        }
-    }
-
-    //See bug 1308253 for reasons.
-    @Suppress
-    public void disableTestOomeSmall() throws Exception {
-        final int SIXTEEN_MB = (16 * 1024 * 1024);
-        final int LINK_SIZE = 6 * 4; // estimated size of a LinkedList's node
-
-        Boolean sawEx = false;
-
-        LinkedList<Object> list = new LinkedList<Object>();
-
-        /* Allocate progressively smaller objects to fill up the entire heap.
-         */
-        int objSize = 1 * 1024 * 1024;
-        while (objSize >= LINK_SIZE) {
-            try {
-                for (int i = 0; i < SIXTEEN_MB / objSize; i++) {
-                    list.add((Object)new byte[objSize]);
-                }
-            } catch (OutOfMemoryError oom) {
-                sawEx = true;
-            }
-
-            if (!sawEx) {
-                throw new RuntimeException("Test failed: " +
-                        "OutOfMemoryError not thrown while filling heap");
-            }
-            sawEx = false;
-
-            objSize = (objSize * 4) / 5;
-        }
-    }
-}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index f201bc7..911347c 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -16,6 +16,16 @@
 
 package com.android.server.connectivity;
 
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
@@ -37,15 +47,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 public class NetworkNotificationManagerTest extends TestCase {
 
     static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
@@ -140,4 +141,47 @@
 
         verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
     }
+
+    @SmallTest
+    public void testDuplicatedNotificationsNoInternetThenSignIn() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        // Show first NO_INTERNET
+        mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(NO_INTERNET.eventId), any(), any());
+
+        // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET
+        mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .cancelAsUser(eq(tag), eq(NO_INTERNET.eventId), any());
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(SIGN_IN.eventId), any(), any());
+
+        // Network disconnects
+        mManager.clearNotification(id);
+        verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
+    }
+
+    @SmallTest
+    public void testDuplicatedNotificationsSignInThenNoInternet() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        // Show first SIGN_IN
+        mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(SIGN_IN.eventId), any(), any());
+        reset(mNotificationManager);
+
+        // NO_INTERNET arrives after, but is ignored.
+        mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, never()).cancelAsUser(any(), anyInt(), any());
+        verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
+
+        // Network disconnects
+        mManager.clearNotification(id);
+        verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
+    }
 }