Merge changes Id931d441,I83faf974

* changes:
  Zygote: Improve logging and error handling during connections.
  Zygote: Fix race condition on package preloads.
diff --git a/Android.mk b/Android.mk
index bd32b55..28e53c6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -487,7 +487,7 @@
 	telecomm/java/com/android/internal/telecom/ITelecomService.aidl \
 	telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl \
         telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl \
-	telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl \
+	telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl \
 	telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl \
         telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl \
 	telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl \
diff --git a/api/current.txt b/api/current.txt
index 6e4890c..093f8b0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -39779,13 +39779,12 @@
     field public static final int STATUS_UNKNOWN = 0; // 0x0
   }
 
-  public class MbmsStreamingManager {
-    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int, android.os.Handler) throws android.telephony.mbms.MbmsException;
-    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, android.os.Handler) 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, android.os.Handler) throws android.telephony.mbms.MbmsException;
+  public class MbmsStreamingSession implements java.lang.AutoCloseable {
+    method public void close();
+    method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, int, android.os.Handler);
+    method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler);
+    method public void requestUpdateStreamingServices(java.util.List<java.lang.String>);
+    method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler);
   }
 
   public class NeighboringCellInfo implements android.os.Parcelable {
@@ -40510,27 +40509,26 @@
     field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
   }
 
-  public class MbmsStreamingManagerCallback {
-    ctor public MbmsStreamingManagerCallback();
+  public class MbmsStreamingSessionCallback {
+    ctor public MbmsStreamingSessionCallback();
     method public void onError(int, java.lang.String);
     method public void onMiddlewareReady();
     method public void onStreamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>);
   }
 
   public class ServiceInfo {
-    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 getServiceClassName();
     method public java.lang.String getServiceId();
     method public java.util.Date getSessionEndTime();
     method public java.util.Date getSessionStartTime();
   }
 
   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;
+    method public android.net.Uri getPlaybackUri();
+    method public void stopStreaming();
     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
diff --git a/api/system-current.txt b/api/system-current.txt
index 5a57171..383a465 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -43209,13 +43209,12 @@
     field public static final int STATUS_UNKNOWN = 0; // 0x0
   }
 
-  public class MbmsStreamingManager {
-    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int, android.os.Handler) throws android.telephony.mbms.MbmsException;
-    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, android.os.Handler) 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, android.os.Handler) throws android.telephony.mbms.MbmsException;
+  public class MbmsStreamingSession implements java.lang.AutoCloseable {
+    method public void close();
+    method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, int, android.os.Handler);
+    method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler);
+    method public void requestUpdateStreamingServices(java.util.List<java.lang.String>);
+    method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler);
     field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
 
@@ -44035,27 +44034,26 @@
     field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
   }
 
-  public class MbmsStreamingManagerCallback {
-    ctor public MbmsStreamingManagerCallback();
+  public class MbmsStreamingSessionCallback {
+    ctor public MbmsStreamingSessionCallback();
     method public void onError(int, java.lang.String);
     method public void onMiddlewareReady();
     method public void onStreamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>);
   }
 
   public class ServiceInfo {
-    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 getServiceClassName();
     method public java.lang.String getServiceId();
     method public java.util.Date getSessionEndTime();
     method public java.util.Date getSessionStartTime();
   }
 
   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;
+    method public android.net.Uri getPlaybackUri();
+    method public void stopStreaming();
     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
@@ -44116,11 +44114,10 @@
   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 initialize(android.telephony.mbms.MbmsStreamingSessionCallback, int) throws android.os.RemoteException;
     method public void onAppCallbackDied(int, int);
+    method public int requestUpdateStreamingServices(int, java.util.List<java.lang.String>) 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;
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index b51a2b7..a50b01d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -40001,13 +40001,12 @@
     field public static final int STATUS_UNKNOWN = 0; // 0x0
   }
 
-  public class MbmsStreamingManager {
-    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int, android.os.Handler) throws android.telephony.mbms.MbmsException;
-    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, android.os.Handler) 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, android.os.Handler) throws android.telephony.mbms.MbmsException;
+  public class MbmsStreamingSession implements java.lang.AutoCloseable {
+    method public void close();
+    method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, int, android.os.Handler);
+    method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler);
+    method public void requestUpdateStreamingServices(java.util.List<java.lang.String>);
+    method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler);
   }
 
   public class NeighboringCellInfo implements android.os.Parcelable {
@@ -40732,27 +40731,26 @@
     field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
   }
 
-  public class MbmsStreamingManagerCallback {
-    ctor public MbmsStreamingManagerCallback();
+  public class MbmsStreamingSessionCallback {
+    ctor public MbmsStreamingSessionCallback();
     method public void onError(int, java.lang.String);
     method public void onMiddlewareReady();
     method public void onStreamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>);
   }
 
   public class ServiceInfo {
-    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 getServiceClassName();
     method public java.lang.String getServiceId();
     method public java.util.Date getSessionEndTime();
     method public java.util.Date getSessionStartTime();
   }
 
   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;
+    method public android.net.Uri getPlaybackUri();
+    method public void stopStreaming();
     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
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 83a82f7..a2af342 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -311,8 +311,7 @@
 
                         for (BluetoothGattService brokenRef : includedServices) {
                             BluetoothGattService includedService = getService(mDevice,
-                                    brokenRef.getUuid(), brokenRef.getInstanceId(),
-                                    brokenRef.getType());
+                                    brokenRef.getUuid(), brokenRef.getInstanceId());
                             if (includedService != null) {
                                 fixedService.addIncludedService(includedService);
                             } else {
@@ -714,10 +713,9 @@
      * @hide
      */
     /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
-            int instanceId, int type) {
+            int instanceId) {
         for (BluetoothGattService svc : mServices) {
             if (svc.getDevice().equals(device)
-                    && svc.getType() == type
                     && svc.getInstanceId() == instanceId
                     && svc.getUuid().equals(uuid)) {
                 return svc;
@@ -913,7 +911,7 @@
 
     /**
      * Set the preferred connection PHY for this app. Please note that this is just a
-     * recommendation, whether the PHY change will happen depends on other applications peferences,
+     * recommendation, whether the PHY change will happen depends on other applications preferences,
      * local and remote controller capabilities. Controller can override these settings.
      * <p>
      * {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index 22eba35..2c8114b 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -184,7 +184,7 @@
     /**
      * Callback indicating the connection parameters were updated.
      *
-     * @param gatt The remote device involved
+     * @param device The remote device involved
      * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
      * 6 (7.5ms) to 3200 (4000ms).
      * @param latency Slave latency for the connection in number of connection events. Valid range
@@ -195,7 +195,7 @@
      * successfully
      * @hide
      */
-    public void onConnectionUpdated(BluetoothDevice gatt, int interval, int latency, int timeout,
+    public void onConnectionUpdated(BluetoothDevice device, int interval, int latency, int timeout,
             int status) {
     }
 
diff --git a/core/java/android/net/metrics/WakeupEvent.java b/core/java/android/net/metrics/WakeupEvent.java
new file mode 100644
index 0000000..cbf3fc8
--- /dev/null
+++ b/core/java/android/net/metrics/WakeupEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+/**
+ * An event logged when NFLOG notifies userspace of a wakeup packet for
+ * watched interfaces.
+ * {@hide}
+ */
+public class WakeupEvent {
+    public String iface;
+    public long timestampMs;
+    public int uid;
+
+    @Override
+    public String toString() {
+        return String.format("WakeupEvent(%tT.%tL, %s, uid: %d)",
+                timestampMs, timestampMs, iface, uid);
+    }
+}
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
new file mode 100644
index 0000000..d520b97
--- /dev/null
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+import android.os.Process;
+import android.os.SystemClock;
+
+/**
+ * An event logged per interface and that aggregates WakeupEvents for that interface.
+ * {@hide}
+ */
+public class WakeupStats {
+
+    private static final int NO_UID = -1;
+
+    public final long creationTimeMs = SystemClock.elapsedRealtime();
+    public final String iface;
+
+    public long totalWakeups = 0;
+    public long rootWakeups = 0;
+    public long systemWakeups = 0;
+    public long nonApplicationWakeups = 0;
+    public long applicationWakeups = 0;
+    public long unroutedWakeups = 0;
+    public long durationSec = 0;
+
+    public WakeupStats(String iface) {
+        this.iface = iface;
+    }
+
+    /** Update durationSec with current time. */
+    public void updateDuration() {
+        durationSec = (SystemClock.elapsedRealtime() - creationTimeMs) / 1000;
+    }
+
+    /** Update wakeup counters for the given WakeupEvent. */
+    public void countEvent(WakeupEvent ev) {
+        totalWakeups++;
+        switch (ev.uid) {
+            case Process.ROOT_UID:
+                rootWakeups++;
+                break;
+            case Process.SYSTEM_UID:
+                systemWakeups++;
+                break;
+            case NO_UID:
+                unroutedWakeups++;
+                break;
+            default:
+                if (ev.uid >= Process.FIRST_APPLICATION_UID) {
+                    applicationWakeups++;
+                } else {
+                    nonApplicationWakeups++;
+                }
+                break;
+        }
+    }
+
+    @Override
+    public String toString() {
+        updateDuration();
+        return new StringBuilder()
+                .append("WakeupStats(").append(iface)
+                .append(", total: ").append(totalWakeups)
+                .append(", root: ").append(rootWakeups)
+                .append(", system: ").append(systemWakeups)
+                .append(", apps: ").append(applicationWakeups)
+                .append(", non-apps: ").append(nonApplicationWakeups)
+                .append(", unrouted: ").append(unroutedWakeups)
+                .append(", ").append(durationSec).append("s)")
+                .toString();
+    }
+}
diff --git a/proto/src/ipconnectivity.proto b/proto/src/ipconnectivity.proto
index 01fb860..d997a80 100644
--- a/proto/src/ipconnectivity.proto
+++ b/proto/src/ipconnectivity.proto
@@ -473,6 +473,38 @@
   repeated Pair validation_states = 8;
 }
 
+// Represents statistics from NFLOG wakeup events due to ingress packets.
+// Since oc-mr1.
+// Next tag: 8.
+message WakeupStats {
+  // The time duration in seconds covered by these stats, for deriving
+  // exact wakeup rates.
+  optional int64 duration_sec = 1;
+
+  // The total number of ingress packets waking up the device.
+  optional int64 total_wakeups = 2;
+
+  // The total number of wakeup packets routed to a socket belonging to
+  // the root uid (uid 0).
+  optional int64 root_wakeups = 3;
+
+  // The total number of wakeup packets routed to a socket belonging to
+  // the system server (uid 1000).
+  optional int64 system_wakeups = 4;
+
+  // The total number of wakeup packets routed to a socket belonging to
+  // an application (uid > 9999).
+  optional int64 application_wakeups = 5;
+
+  // The total number of wakeup packets routed to a socket belonging to another
+  // uid than the root uid, system uid, or an application uid (any uid in
+  // between [1001, 9999]. See android.os.Process for possible uids.
+  optional int64 non_application_wakeups = 6;
+
+  // The total number of wakeup packets with no associated sockets.
+  optional int64 unrouted_wakeups = 7;
+}
+
 // Represents one of the IP connectivity event defined in this file.
 // Next tag: 20
 message IpConnectivityEvent {
@@ -547,6 +579,9 @@
 
     // Network statistics.
     NetworkStats network_stats = 19;
+
+    // Ingress packet wakeup statistics.
+    WakeupStats wakeup_stats = 20;
   };
 };
 
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index ee38219..22330e6 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -38,6 +38,7 @@
 import android.net.metrics.NetworkEvent;
 import android.net.metrics.RaEvent;
 import android.net.metrics.ValidationProbeEvent;
+import android.net.metrics.WakeupStats;
 import android.os.Parcelable;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -115,6 +116,22 @@
         return out;
     }
 
+    public static IpConnectivityEvent toProto(WakeupStats in) {
+        IpConnectivityLogClass.WakeupStats wakeupStats =
+                new IpConnectivityLogClass.WakeupStats();
+        in.updateDuration();
+        wakeupStats.durationSec = in.durationSec;
+        wakeupStats.totalWakeups = in.totalWakeups;
+        wakeupStats.rootWakeups = in.rootWakeups;
+        wakeupStats.systemWakeups = in.systemWakeups;
+        wakeupStats.nonApplicationWakeups = in.nonApplicationWakeups;
+        wakeupStats.applicationWakeups = in.applicationWakeups;
+        wakeupStats.unroutedWakeups = in.unroutedWakeups;
+        final IpConnectivityEvent out = buildEvent(0, 0, in.iface);
+        out.setWakeupStats(wakeupStats);
+        return out;
+    }
+
     private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) {
         final IpConnectivityEvent ev = new IpConnectivityEvent();
         ev.networkId = netId;
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 4094083..6f7ace2 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity;
 
+import static android.util.TimeUtils.NANOS_PER_MS;
+
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.INetdEventCallback;
@@ -25,9 +27,12 @@
 import android.net.metrics.DnsEvent;
 import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.WakeupEvent;
+import android.net.metrics.WakeupStats;
 import android.os.RemoteException;
 import android.text.format.DateUtils;
 import android.util.Log;
+import android.util.ArrayMap;
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -59,12 +64,28 @@
     private static final int CONNECT_LATENCY_FILL_RATE    = 15 * (int) DateUtils.SECOND_IN_MILLIS;
     private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
 
+    @VisibleForTesting
+    static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024;
+    // TODO: dedup this String constant with the one used in
+    // ConnectivityService#wakeupModifyInterface().
+    @VisibleForTesting
+    static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:";
+
     // Sparse arrays of DNS and connect events, grouped by net id.
     @GuardedBy("this")
     private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>();
     @GuardedBy("this")
     private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>();
 
+    // Array of aggregated wakeup event stats, grouped by interface name.
+    @GuardedBy("this")
+    private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>();
+    // Ring buffer array for storing packet wake up events sent by Netd.
+    @GuardedBy("this")
+    private final WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH];
+    @GuardedBy("this")
+    private long mWakeupEventCursor = 0;
+
     private final ConnectivityManager mCm;
 
     @GuardedBy("this")
@@ -137,11 +158,62 @@
 
     @Override
     public synchronized void onWakeupEvent(String prefix, int uid, int gid, long timestampNs) {
+        maybeVerboseLog("onWakeupEvent(%s, %d, %d, %sns)", prefix, uid, gid, timestampNs);
+
+        // TODO: add ip protocol and port
+
+        String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, "");
+        final long timestampMs;
+        if (timestampNs > 0) {
+            timestampMs = timestampNs / NANOS_PER_MS;
+        } else {
+            timestampMs = System.currentTimeMillis();
+        }
+
+        addWakupEvent(iface, timestampMs, uid);
+    }
+
+    @GuardedBy("this")
+    private void addWakupEvent(String iface, long timestampMs, int uid) {
+        int index = wakeupEventIndex(mWakeupEventCursor);
+        mWakeupEventCursor++;
+        WakeupEvent event = new WakeupEvent();
+        event.iface = iface;
+        event.timestampMs = timestampMs;
+        event.uid = uid;
+        mWakeupEvents[index] = event;
+        WakeupStats stats = mWakeupStats.get(iface);
+        if (stats == null) {
+            stats = new WakeupStats(iface);
+            mWakeupStats.put(iface, stats);
+        }
+        stats.countEvent(event);
+    }
+
+    @GuardedBy("this")
+    private WakeupEvent[] getWakeupEvents() {
+        int length = (int) Math.min(mWakeupEventCursor, (long) mWakeupEvents.length);
+        WakeupEvent[] out = new WakeupEvent[length];
+        // Reverse iteration from youngest event to oldest event.
+        long inCursor = mWakeupEventCursor - 1;
+        int outIdx = out.length - 1;
+        while (outIdx >= 0) {
+            out[outIdx--] = mWakeupEvents[wakeupEventIndex(inCursor--)];
+        }
+        return out;
+    }
+
+    private static int wakeupEventIndex(long cursor) {
+        return (int) Math.abs(cursor % WAKEUP_EVENT_BUFFER_LENGTH);
     }
 
     public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
         flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
         flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
+        for (int i = 0; i < mWakeupStats.size(); i++) {
+            events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
+        }
+        mWakeupStats.clear();
     }
 
     public synchronized void dump(PrintWriter writer) {
@@ -153,13 +225,22 @@
     }
 
     public synchronized void list(PrintWriter pw) {
-        listEvents(pw, mConnectEvents, (x) -> x);
-        listEvents(pw, mDnsEvents, (x) -> x);
+        listEvents(pw, mConnectEvents, (x) -> x, "\n");
+        listEvents(pw, mDnsEvents, (x) -> x, "\n");
+        for (int i = 0; i < mWakeupStats.size(); i++) {
+            pw.println(mWakeupStats.valueAt(i));
+        }
+        for (WakeupEvent wakeup : getWakeupEvents()) {
+            pw.println(wakeup);
+        }
     }
 
     public synchronized void listAsProtos(PrintWriter pw) {
-        listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto);
-        listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto);
+        listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto, "");
+        listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto, "");
+        for (int i = 0; i < mWakeupStats.size(); i++) {
+            pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
+        }
     }
 
     private static <T> void flushProtos(List<IpConnectivityEvent> out, SparseArray<T> in,
@@ -170,10 +251,13 @@
         in.clear();
     }
 
-    public static <T> void listEvents(
-            PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper) {
+    private static <T> void listEvents(
+            PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper, String separator) {
+        // Proto derived Classes have toString method that adds a \n at the end.
+        // Let the caller control that by passing in the line separator explicitly.
         for (int i = 0; i < events.size(); i++) {
-            pw.println(mapper.apply(events.valueAt(i)).toString());
+            pw.print(mapper.apply(events.valueAt(i)));
+            pw.print(separator);
         }
     }
 
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index e96f4b0..a4d7242 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -278,6 +278,10 @@
         return mHandler;
     }
 
+    public Network network() {
+        return network;
+    }
+
     // Functions for manipulating the requests satisfied by this network.
     //
     // These functions must only called on ConnectivityService's main thread.
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index d3a9354..8b886d6 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -20,7 +20,6 @@
 import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
 
-import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -229,6 +228,8 @@
     // Delay between reevaluations once a captive portal has been found.
     private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10*60*1000;
 
+    private static final int NUM_VALIDATION_LOG_LINES = 20;
+
     private final Context mContext;
     private final Handler mConnectivityServiceHandler;
     private final NetworkAgentInfo mNetworkAgentInfo;
@@ -236,9 +237,15 @@
     private final int mNetId;
     private final TelephonyManager mTelephonyManager;
     private final WifiManager mWifiManager;
-    private final AlarmManager mAlarmManager;
     private final NetworkRequest mDefaultRequest;
     private final IpConnectivityLog mMetricsLog;
+    private final NetworkMonitorSettings mSettings;
+
+    // Configuration values for captive portal detection probes.
+    private final String mCaptivePortalUserAgent;
+    private final URL mCaptivePortalHttpsUrl;
+    private final URL mCaptivePortalHttpUrl;
+    private final URL[] mCaptivePortalFallbackUrls;
 
     @VisibleForTesting
     protected boolean mIsCaptivePortalCheckEnabled;
@@ -262,40 +269,37 @@
 
     private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
 
-    private final LocalLog validationLogs = new LocalLog(20); // 20 lines
+    private final LocalLog validationLogs = new LocalLog(NUM_VALIDATION_LOG_LINES);
 
     private final Stopwatch mEvaluationTimer = new Stopwatch();
 
     // This variable is set before transitioning to the mCaptivePortalState.
     private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
 
-    // Configuration values for captive portal detection probes.
-    private final String mCaptivePortalUserAgent;
-    private final URL mCaptivePortalHttpsUrl;
-    private final URL mCaptivePortalHttpUrl;
-    private final URL[] mCaptivePortalFallbackUrls;
     private int mNextFallbackUrlIndex = 0;
 
     public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
             NetworkRequest defaultRequest) {
-        this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog());
+        this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog(),
+                NetworkMonitorSettings.DEFAULT);
     }
 
     @VisibleForTesting
     protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
-            NetworkRequest defaultRequest, IpConnectivityLog logger) {
+            NetworkRequest defaultRequest, IpConnectivityLog logger,
+            NetworkMonitorSettings settings) {
         // Add suffix indicating which NetworkMonitor we're talking about.
         super(TAG + networkAgentInfo.name());
 
         mContext = context;
         mMetricsLog = logger;
         mConnectivityServiceHandler = handler;
+        mSettings = settings;
         mNetworkAgentInfo = networkAgentInfo;
-        mNetwork = new OneAddressPerFamilyNetwork(networkAgentInfo.network);
+        mNetwork = new OneAddressPerFamilyNetwork(networkAgentInfo.network());
         mNetId = mNetwork.netId;
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mDefaultRequest = defaultRequest;
 
         addState(mDefaultState);
@@ -305,16 +309,12 @@
             addState(mCaptivePortalState, mMaybeNotifyState);
         setInitialState(mDefaultState);
 
-        mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.CAPTIVE_PORTAL_MODE, Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT)
-                != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
-        mUseHttps = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
-
-        mCaptivePortalUserAgent = getCaptivePortalUserAgent(context);
-        mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl(context));
-        mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(context));
-        mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls(context);
+        mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
+        mUseHttps = getUseHttpsValidation();
+        mCaptivePortalUserAgent = getCaptivePortalUserAgent();
+        mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
+        mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(settings, context));
+        mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
 
         start();
     }
@@ -705,19 +705,42 @@
         }
     }
 
-    private static String getCaptivePortalServerHttpsUrl(Context context) {
-        return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
+    public boolean getIsCaptivePortalCheckEnabled() {
+        String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
+        int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
+        int mode = mSettings.getSetting(mContext, symbol, defaultValue);
+        return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
     }
 
+    public boolean getUseHttpsValidation() {
+        return mSettings.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
+    }
+
+    public boolean getWifiScansAlwaysAvailableDisabled() {
+        return mSettings.getSetting(mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
+    }
+
+    private String getCaptivePortalServerHttpsUrl() {
+        return mSettings.getSetting(mContext,
+                Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
+    }
+
+    // Static for direct access by ConnectivityService
     public static String getCaptivePortalServerHttpUrl(Context context) {
-        return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
+        return getCaptivePortalServerHttpUrl(NetworkMonitorSettings.DEFAULT, context);
     }
 
-    private URL[] makeCaptivePortalFallbackUrls(Context context) {
+    public static String getCaptivePortalServerHttpUrl(
+            NetworkMonitorSettings settings, Context context) {
+        return settings.getSetting(
+                context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
+    }
+
+    private URL[] makeCaptivePortalFallbackUrls() {
         String separator = ",";
-        String firstUrl = getSetting(context,
+        String firstUrl = mSettings.getSetting(mContext,
                 Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL);
-        String joinedUrls = firstUrl + separator + getSetting(context,
+        String joinedUrls = firstUrl + separator + mSettings.getSetting(mContext,
                 Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS, DEFAULT_OTHER_FALLBACK_URLS);
         List<URL> urls = new ArrayList<>();
         for (String s : joinedUrls.split(separator)) {
@@ -733,13 +756,9 @@
         return urls.toArray(new URL[urls.size()]);
     }
 
-    private static String getCaptivePortalUserAgent(Context context) {
-        return getSetting(context, Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
-    }
-
-    private static String getSetting(Context context, String symbol, String defaultValue) {
-        final String value = Settings.Global.getString(context.getContentResolver(), symbol);
-        return value != null ? value : defaultValue;
+    private String getCaptivePortalUserAgent() {
+        return mSettings.getSetting(mContext,
+                Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
     }
 
     private URL nextFallbackUrl() {
@@ -1035,12 +1054,13 @@
      */
     private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
             long requestTimestampMs, long responseTimestampMs) {
-        if (Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) {
+        if (getWifiScansAlwaysAvailableDisabled()) {
             return;
         }
 
-        if (systemReady == false) return;
+        if (!systemReady) {
+            return;
+        }
 
         Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED);
         switch (mNetworkAgentInfo.networkInfo.getType()) {
@@ -1144,4 +1164,24 @@
         ev.durationMs = durationMs;
         mMetricsLog.log(mNetId, transports, ev);
     }
+
+    @VisibleForTesting
+    public interface NetworkMonitorSettings {
+        int getSetting(Context context, String symbol, int defaultValue);
+        String getSetting(Context context, String symbol, String defaultValue);
+
+        static NetworkMonitorSettings DEFAULT = new DefaultNetworkMonitorSettings();
+    }
+
+    @VisibleForTesting
+    public static class DefaultNetworkMonitorSettings implements NetworkMonitorSettings {
+        public int getSetting(Context context, String symbol, int defaultValue) {
+            return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue);
+        }
+
+        public String getSetting(Context context, String symbol, String defaultValue) {
+            final String value = Settings.Global.getString(context.getContentResolver(), symbol);
+            return value != null ? value : defaultValue;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
index 11928b9..6db70cd 100644
--- a/services/core/java/com/android/server/timezone/IntentHelperImpl.java
+++ b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
@@ -53,20 +53,34 @@
         // The intent filter that triggers when package update events happen that indicate there may
         // be work to do.
         IntentFilter packageIntentFilter = new IntentFilter();
-        // Either of these mean a downgrade?
-        packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+
         packageIntentFilter.addDataScheme("package");
         packageIntentFilter.addDataSchemeSpecificPart(
                 updaterAppPackageName, PatternMatcher.PATTERN_LITERAL);
         packageIntentFilter.addDataSchemeSpecificPart(
                 dataAppPackageName, PatternMatcher.PATTERN_LITERAL);
+
+        // ACTION_PACKAGE_ADDED is fired when a package is upgraded or downgraded (in addition to
+        // ACTION_PACKAGE_REMOVED and ACTION_PACKAGE_REPLACED). A system/priv-app can never be
+        // removed entirely so we do not need to trigger on ACTION_PACKAGE_REMOVED or
+        // ACTION_PACKAGE_FULLY_REMOVED.
+        packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+
+        // ACTION_PACKAGE_CHANGED is used when a package is disabled / re-enabled. It is not
+        // strictly necessary to trigger on this but it won't hurt anything and may catch some cases
+        // where a package has changed while disabled.
+        // Note: ACTION_PACKAGE_CHANGED is not fired when updating a suspended app, but
+        // ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and ACTION_PACKAGE_REPLACED are (and the app
+        // is left in an unsuspended state after this).
+        packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+
+        // We do not register for ACTION_PACKAGE_RESTARTED because it doesn't imply an update.
+        // We do not register for ACTION_PACKAGE_DATA_CLEARED because the updater / data apps are
+        // not expected to need local data.
+
         Receiver packageUpdateReceiver = new Receiver(listener, true /* packageUpdated */);
         mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
 
-        // TODO(nfuller): Add more exotic intents as needed. e.g.
-        // packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        // Also, disabled...?
         mReliabilityReceiver = new Receiver(listener, false /* packageUpdated */);
     }
 
diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingSession.java
similarity index 62%
rename from telephony/java/android/telephony/MbmsStreamingManager.java
rename to telephony/java/android/telephony/MbmsStreamingSession.java
index b6b253e..5550ac1 100644
--- a/telephony/java/android/telephony/MbmsStreamingManager.java
+++ b/telephony/java/android/telephony/MbmsStreamingSession.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
@@ -25,18 +27,20 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.telephony.mbms.InternalStreamingManagerCallback;
+import android.telephony.mbms.InternalStreamingSessionCallback;
 import android.telephony.mbms.InternalStreamingServiceCallback;
 import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.MbmsStreamingSessionCallback;
 import android.telephony.mbms.MbmsUtils;
 import android.telephony.mbms.StreamingService;
 import android.telephony.mbms.StreamingServiceCallback;
 import android.telephony.mbms.StreamingServiceInfo;
 import android.telephony.mbms.vendor.IMbmsStreamingService;
+import android.util.ArraySet;
 import android.util.Log;
 
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -45,8 +49,8 @@
 /**
  * This class provides functionality for streaming media over MBMS.
  */
-public class MbmsStreamingManager {
-    private static final String LOG_TAG = "MbmsStreamingManager";
+public class MbmsStreamingSession implements AutoCloseable {
+    private static final String LOG_TAG = "MbmsStreamingSession";
 
     /**
      * Service action which must be handled by the middleware implementing the MBMS streaming
@@ -69,94 +73,94 @@
         }
     };
 
-    private InternalStreamingManagerCallback mInternalCallback;
+    private InternalStreamingSessionCallback mInternalCallback;
+    private Set<StreamingService> mKnownActiveStreamingServices = new ArraySet<>();
 
     private final Context mContext;
     private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
 
     /** @hide */
-    private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback,
+    private MbmsStreamingSession(Context context, MbmsStreamingSessionCallback callback,
                     int subscriptionId, Handler handler) {
         mContext = context;
         mSubscriptionId = subscriptionId;
         if (handler == null) {
             handler = new Handler(Looper.getMainLooper());
         }
-        mInternalCallback = new InternalStreamingManagerCallback(callback, handler);
+        mInternalCallback = new InternalStreamingSessionCallback(callback, handler);
     }
 
     /**
-     * Create a new MbmsStreamingManager using the given subscription ID.
+     * Create a new {@link MbmsStreamingSession} using the given subscription ID.
      *
      * Note that this call will bind a remote service. You may not call this method on your app's
-     * main thread. This may throw an {@link MbmsException}, indicating errors that may happen
-     * during the initialization or binding process.
+     * main thread.
      *
-     *
-     * You may only have one instance of {@link MbmsStreamingManager} per UID. If you call this
-     * method while there is an active instance of {@link MbmsStreamingManager} in your process
-     * (in other words, one that has not had {@link #dispose()} called on it), this method will
-     * throw an {@link MbmsException}. If you call this method in a different process
+     * You may only have one instance of {@link MbmsStreamingSession} per UID. If you call this
+     * method while there is an active instance of {@link MbmsStreamingSession} in your process
+     * (in other words, one that has not had {@link #close()} called on it), this method will
+     * throw an {@link IllegalStateException}. If you call this method in a different process
      * running under the same UID, an error will be indicated via
-     * {@link MbmsStreamingManagerCallback#onError(int, String)}.
+     * {@link MbmsStreamingSessionCallback#onError(int, String)}.
      *
      * Note that initialization may fail asynchronously. If you wish to try again after you
-     * receive such an asynchronous error, you must call dispose() on the instance of
-     * {@link MbmsStreamingManager} that you received before calling this method again.
+     * receive such an asynchronous error, you must call {@link #close()} on the instance of
+     * {@link MbmsStreamingSession} that you received before calling this method again.
      *
      * @param context The {@link Context} to use.
      * @param callback A callback object on which you wish to receive results of asynchronous
      *                 operations.
      * @param subscriptionId The subscription ID to use.
-     * @param handler The handler you wish to receive callbacks on. If null, callbacks will be
-     *                processed on the main looper (in other words, the looper returned from
-     *                {@link Looper#getMainLooper()}).
+     * @param handler The handler you wish to receive callbacks on.
+     * @return An instance of {@link MbmsStreamingSession}, or null if an error occurred.
      */
-    public static MbmsStreamingManager create(Context context,
-            MbmsStreamingManagerCallback callback, int subscriptionId, Handler handler)
-            throws MbmsException {
+    public static @Nullable MbmsStreamingSession create(@NonNull Context context,
+            @NonNull MbmsStreamingSessionCallback callback, int subscriptionId,
+            @NonNull Handler handler) {
         if (!sIsInitialized.compareAndSet(false, true)) {
-            throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
+            throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession");
         }
-        MbmsStreamingManager manager = new MbmsStreamingManager(context, callback,
+        MbmsStreamingSession session = new MbmsStreamingSession(context, callback,
                 subscriptionId, handler);
-        try {
-            manager.bindAndInitialize();
-        } catch (MbmsException e) {
+
+        int result = session.bindAndInitialize();
+        if (result != MbmsException.SUCCESS) {
             sIsInitialized.set(false);
-            throw e;
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onError(result, null);
+                }
+            });
+            return null;
         }
-        return manager;
+        return session;
     }
 
     /**
-     * Create a new MbmsStreamingManager using the system default data subscription ID.
-     * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
+     * Create a new {@link MbmsStreamingSession} using the system default data subscription ID.
+     * See {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)}.
      */
-    public static MbmsStreamingManager create(Context context,
-            MbmsStreamingManagerCallback callback, Handler handler)
-            throws MbmsException {
+    public static MbmsStreamingSession create(@NonNull Context context,
+            @NonNull MbmsStreamingSessionCallback callback, @NonNull Handler handler) {
         return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
     }
 
     /**
-     * Create a new MbmsStreamingManager using the system default data subscription ID and
-     * default {@link Handler}.
-     * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
-     */
-    public static MbmsStreamingManager create(Context context,
-            MbmsStreamingManagerCallback callback)
-            throws MbmsException {
-        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null);
-    }
-
-    /**
-     * Terminates this instance, ending calls to the registered listener.  Also terminates
-     * any streaming services spawned from this instance.
+     * Terminates this instance. Also terminates
+     * any streaming services spawned from this instance as if
+     * {@link StreamingService#stopStreaming()} had been called on them. After this method returns,
+     * no further callbacks originating from the middleware will be enqueued on the provided
+     * instance of {@link MbmsStreamingSessionCallback}, but callbacks that have already been
+     * enqueued will still be delivered.
+     *
+     * It is safe to call {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)} to
+     * obtain another instance of {@link MbmsStreamingSession} immediately after this method
+     * returns.
      *
      * May throw an {@link IllegalStateException}
      */
-    public void dispose() {
+    public void close() {
         try {
             IMbmsStreamingService streamingService = mService.get();
             if (streamingService == null) {
@@ -164,47 +168,49 @@
                 return;
             }
             streamingService.dispose(mSubscriptionId);
+            for (StreamingService s : mKnownActiveStreamingServices) {
+                s.getCallback().stop();
+            }
+            mKnownActiveStreamingServices.clear();
         } catch (RemoteException e) {
             // Ignore for now
         } finally {
             mService.set(null);
             sIsInitialized.set(false);
+            mInternalCallback.stop();
         }
     }
 
     /**
      * An inspection API to retrieve the list of streaming media currently be advertised.
-     * The results are returned asynchronously through the previously registered callback.
-     * serviceClasses lets the app filter on types of programming and is opaque data between
-     * the app and the carrier.
+     * The results are returned asynchronously via
+     * {@link MbmsStreamingSessionCallback#onStreamingServicesUpdated(List)} on the callback
+     * provided upon creation.
      *
-     * Multiple calls replace the list of serviceClasses of interest.
+     * Multiple calls replace the list of service classes of interest.
      *
-     * This may throw an {@link MbmsException} containing any error in
-     * {@link android.telephony.mbms.MbmsException.GeneralErrors},
-     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
      *
-     * May also throw an unchecked {@link IllegalArgumentException} or an
-     * {@link IllegalStateException}
-     *
-     * @param classList A list of streaming service classes that the app would like updates on.
+     * @param serviceClassList A list of streaming service classes that the app would like updates
+     *                         on. The exact names of these classes should be negotiated with the
+     *                         wireless carrier separately.
      */
-    public void getStreamingServices(List<String> classList) throws MbmsException {
+    public void requestUpdateStreamingServices(List<String> serviceClassList) {
         IMbmsStreamingService streamingService = mService.get();
         if (streamingService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+            throw new IllegalStateException("Middleware not yet bound");
         }
         try {
-            int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList);
+            int returnCode = streamingService.requestUpdateStreamingServices(
+                    mSubscriptionId, serviceClassList);
             if (returnCode != MbmsException.SUCCESS) {
-                throw new MbmsException(returnCode);
+                sendErrorToApp(returnCode, null);
             }
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService.set(null);
             sIsInitialized.set(false);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
         }
     }
 
@@ -215,13 +221,7 @@
      * reported via
      * {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}
      *
-     * May throw an
-     * {@link MbmsException} containing any of the error codes in
-     * {@link android.telephony.mbms.MbmsException.GeneralErrors},
-     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
-     *
-     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
      * Asynchronous errors through the callback include any of the errors in
      * {@link android.telephony.mbms.MbmsException.GeneralErrors} or
@@ -229,42 +229,49 @@
      *
      * @param serviceInfo The information about the service to stream.
      * @param callback A callback that'll be called when something about the stream changes.
-     * @param handler A handler that calls to {@code callback} should be called on. If null,
-     *                defaults to the handler provided via
-     *                {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
+     * @param handler A handler that calls to {@code callback} should be called on.
      * @return An instance of {@link StreamingService} through which the stream can be controlled.
+     *         May be {@code null} if an error occurred.
      */
-    public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
-            StreamingServiceCallback callback, Handler handler) throws MbmsException {
+    public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo,
+            StreamingServiceCallback callback, @NonNull Handler handler) {
         IMbmsStreamingService streamingService = mService.get();
         if (streamingService == null) {
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+            throw new IllegalStateException("Middleware not yet bound");
         }
 
         InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
-                callback, handler == null ? mInternalCallback.getHandler() : handler);
+                callback, handler);
 
         StreamingService serviceForApp = new StreamingService(
-                mSubscriptionId, streamingService, serviceInfo, serviceCallback);
+                mSubscriptionId, streamingService, this, serviceInfo, serviceCallback);
+        mKnownActiveStreamingServices.add(serviceForApp);
 
         try {
             int returnCode = streamingService.startStreaming(
                     mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
             if (returnCode != MbmsException.SUCCESS) {
-                throw new MbmsException(returnCode);
+                sendErrorToApp(returnCode, null);
+                return null;
             }
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService.set(null);
             sIsInitialized.set(false);
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
+            return null;
         }
 
         return serviceForApp;
     }
 
-    private void bindAndInitialize() throws MbmsException {
-        MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
+    /** @hide */
+    public void onStreamingServiceStopped(StreamingService service) {
+        mKnownActiveStreamingServices.remove(service);
+    }
+
+    private int bindAndInitialize() {
+        return MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
                 new ServiceConnection() {
                     @Override
                     public void onServiceConnected(ComponentName name, IBinder service) {
@@ -315,7 +322,7 @@
 
     private void sendErrorToApp(int errorCode, String message) {
         try {
-            mInternalCallback.error(errorCode, message);
+            mInternalCallback.onError(errorCode, message);
         } catch (RemoteException e) {
             // Ignore, should not happen locally.
         }
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index 9300ef9..d8d7f48 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -58,7 +58,7 @@
     FileServiceInfo(Parcel in) {
         super(in);
         files = new ArrayList<FileInfo>();
-        in.readList(files, null);
+        in.readList(files, FileInfo.class.getClassLoader());
     }
 
     @Override
diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl
similarity index 80%
rename from telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
rename to telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl
index 007aee7..0bf0ebc 100755
--- a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl
@@ -24,11 +24,11 @@
  * The interface the clients top-level streaming listener will satisfy.
  * @hide
  */
-oneway interface IMbmsStreamingManagerCallback
+oneway interface IMbmsStreamingSessionCallback
 {
-    void error(int errorCode, String message);
+    void onError(int errorCode, String message);
 
-    void streamingServicesUpdated(in List<StreamingServiceInfo> services);
+    void onStreamingServicesUpdated(in List<StreamingServiceInfo> services);
 
-    void middlewareReady();
+    void onMiddlewareReady();
 }
diff --git a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
index 0952fbe..164cefb 100755
--- a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
@@ -20,9 +20,9 @@
  * @hide
  */
 oneway interface IStreamingServiceCallback {
-    void error(int errorCode, String message);
-    void streamStateUpdated(int state, int reason);
-    void mediaDescriptionUpdated();
-    void broadcastSignalStrengthUpdated(int signalStrength);
-    void streamMethodUpdated(int methodType);
+    void onError(int errorCode, String message);
+    void onStreamStateUpdated(int state, int reason);
+    void onMediaDescriptionUpdated();
+    void onBroadcastSignalStrengthUpdated(int signalStrength);
+    void onStreamMethodUpdated(int methodType);
 }
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
index bb337b2..28ee5f1 100644
--- a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
@@ -23,6 +23,7 @@
 public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub {
     private final StreamingServiceCallback mAppCallback;
     private final Handler mHandler;
+    private volatile boolean mIsStopped = false;
 
     public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) {
         mAppCallback = appCallback;
@@ -30,7 +31,11 @@
     }
 
     @Override
-    public void error(int errorCode, String message) throws RemoteException {
+    public void onError(int errorCode, String message) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -40,7 +45,11 @@
     }
 
     @Override
-    public void streamStateUpdated(int state, int reason) throws RemoteException {
+    public void onStreamStateUpdated(int state, int reason) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -50,7 +59,11 @@
     }
 
     @Override
-    public void mediaDescriptionUpdated() throws RemoteException {
+    public void onMediaDescriptionUpdated() throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -60,7 +73,11 @@
     }
 
     @Override
-    public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
+    public void onBroadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -70,7 +87,11 @@
     }
 
     @Override
-    public void streamMethodUpdated(int methodType) throws RemoteException {
+    public void onStreamMethodUpdated(int methodType) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -78,4 +99,8 @@
             }
         });
     }
+
+    public void stop() {
+        mIsStopped = true;
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
similarity index 69%
rename from telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java
rename to telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
index b52df8c..0d890b8 100644
--- a/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
@@ -18,25 +18,27 @@
 
 import android.os.Handler;
 import android.os.RemoteException;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
-import android.telephony.mbms.StreamingServiceInfo;
 
 import java.util.List;
 
 /** @hide */
-public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
+public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallback.Stub {
     private final Handler mHandler;
-    private final MbmsStreamingManagerCallback mAppCallback;
+    private final MbmsStreamingSessionCallback mAppCallback;
+    private volatile boolean mIsStopped = false;
 
-    public InternalStreamingManagerCallback(MbmsStreamingManagerCallback appCallback,
+    public InternalStreamingSessionCallback(MbmsStreamingSessionCallback appCallback,
             Handler handler) {
         mAppCallback = appCallback;
         mHandler = handler;
     }
 
     @Override
-    public void error(int errorCode, String message) throws RemoteException {
+    public void onError(int errorCode, String message) throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -46,8 +48,12 @@
     }
 
     @Override
-    public void streamingServicesUpdated(List<StreamingServiceInfo> services)
+    public void onStreamingServicesUpdated(List<StreamingServiceInfo> services)
             throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -57,7 +63,11 @@
     }
 
     @Override
-    public void middlewareReady() throws RemoteException {
+    public void onMiddlewareReady() throws RemoteException {
+        if (mIsStopped) {
+            return;
+        }
+
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -69,4 +79,8 @@
     public Handler getHandler() {
         return mHandler;
     }
+
+    public void stop() {
+        mIsStopped = true;
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java
index 56cd8de..5b069cb 100644
--- a/telephony/java/android/telephony/mbms/MbmsException.java
+++ b/telephony/java/android/telephony/mbms/MbmsException.java
@@ -16,6 +16,8 @@
 
 package android.telephony.mbms;
 
+import android.telephony.MbmsStreamingSession;
+
 public class MbmsException extends Exception {
     /** Indicates that the operation was successful. */
     public static final int SUCCESS = 0;
@@ -31,7 +33,7 @@
     /**
      * Indicates that the app attempted to perform an operation on an instance of
      * TODO link android.telephony.MbmsDownloadManager or
-     * {@link android.telephony.MbmsStreamingManager} without being bound to the middleware.
+     * {@link MbmsStreamingSession} without being bound to the middleware.
      */
     public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2;
 
@@ -46,7 +48,7 @@
         private InitializationErrors() {}
         /**
          * Indicates that the app tried to create more than one instance each of
-         * {@link android.telephony.MbmsStreamingManager} or
+         * {@link MbmsStreamingSession} or
          * TODO link android.telephony.MbmsDownloadManager
          */
         public static final int ERROR_DUPLICATE_INITIALIZE = 101;
@@ -64,7 +66,7 @@
         private GeneralErrors() {}
         /**
          * Indicates that the app attempted to perform an operation before receiving notification
-         * that the middleware is ready via {@link MbmsStreamingManagerCallback#onMiddlewareReady()}
+         * that the middleware is ready via {@link MbmsStreamingSessionCallback#onMiddlewareReady()}
          * or TODO: link MbmsDownloadManagerCallback#middlewareReady
          */
         public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201;
@@ -107,7 +109,7 @@
 
         /**
          * Indicates that the app called
-         * {@link android.telephony.MbmsStreamingManager#startStreaming(
+         * {@link MbmsStreamingSession#startStreaming(
          * StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)}
          * more than once for the same {@link StreamingServiceInfo}.
          */
diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java
similarity index 76%
rename from telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
rename to telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java
index b31ffa7..4a70c00 100644
--- a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java
@@ -16,25 +16,26 @@
 
 package android.telephony.mbms;
 
+import android.annotation.Nullable;
 import android.content.Context;
-import android.os.RemoteException;
-import android.telephony.MbmsStreamingManager;
+import android.os.Handler;
+import android.telephony.MbmsStreamingSession;
 
 import java.util.List;
 
 /**
  * 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)}.
+ * {@link MbmsStreamingSession#create(Context, MbmsStreamingSessionCallback, int, Handler)}.
  */
-public class MbmsStreamingManagerCallback {
+public class MbmsStreamingSessionCallback {
     /**
      * 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 onError(int errorCode, String message) {
+    public void onError(int errorCode, @Nullable String message) {
         // default implementation empty
     }
 
@@ -47,8 +48,7 @@
      * call with the same service class list would return different
      * results.
      *
-     * @param services a List of StreamingServiceInfos
-     *
+     * @param services The list of available services.
      */
     public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
         // default implementation empty
@@ -58,9 +58,9 @@
      * Called to indicate that the middleware has been initialized and is ready.
      *
      * Before this method is called, calling any method on an instance of
-     * {@link android.telephony.MbmsStreamingManager} will result in an {@link MbmsException}
-     * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
-     * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+     * {@link MbmsStreamingSession} will result in an {@link IllegalStateException} or an error
+     * delivered via {@link #onError(int, String)} with error code
+     * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}.
      */
     public void onMiddlewareReady() {
         // default implementation empty
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index 4b913f8..8652b3e 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -68,19 +68,20 @@
         return downloadServices.get(0).serviceInfo;
     }
 
-    public static void startBinding(Context context, String serviceAction,
-            ServiceConnection serviceConnection) throws MbmsException {
+    public static int startBinding(Context context, String serviceAction,
+            ServiceConnection serviceConnection) {
         Intent bindIntent = new Intent();
         ServiceInfo mbmsServiceInfo =
                 MbmsUtils.getMiddlewareServiceInfo(context, serviceAction);
 
         if (mbmsServiceInfo == null) {
-            throw new MbmsException(MbmsException.ERROR_NO_UNIQUE_MIDDLEWARE);
+            return MbmsException.ERROR_NO_UNIQUE_MIDDLEWARE;
         }
 
         bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo));
 
         context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
+        return MbmsException.SUCCESS;
     }
 
     /**
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 5764cfb..e5d1d3b 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -123,7 +123,7 @@
     /**
      * The class name for this service - used to categorize and filter
      */
-    public String getClassName() {
+    public String getServiceClassName() {
         return className;
     }
 
diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java
index 1d66bac..1a36fc1 100644
--- a/telephony/java/android/telephony/mbms/StreamingService.java
+++ b/telephony/java/android/telephony/mbms/StreamingService.java
@@ -17,8 +17,10 @@
 package android.telephony.mbms;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.net.Uri;
 import android.os.RemoteException;
+import android.telephony.MbmsStreamingSession;
 import android.telephony.mbms.vendor.IMbmsStreamingService;
 import android.util.Log;
 
@@ -27,7 +29,7 @@
 
 /**
  * Class used to represent a single MBMS stream. After a stream has been started with
- * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+ * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
  * StreamingServiceCallback, android.os.Handler)},
  * this class is used to hold information about the stream and control it.
  */
@@ -63,7 +65,7 @@
 
     /**
      * State changed due to a call to {@link #stopStreaming()} or
-     * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+     * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
      * StreamingServiceCallback, android.os.Handler)}
      */
     public static final int REASON_BY_USER_REQUEST = 1;
@@ -101,6 +103,7 @@
     public final static int UNICAST_METHOD   = 2;
 
     private final int mSubscriptionId;
+    private final MbmsStreamingSession mParentSession;
     private final StreamingServiceInfo mServiceInfo;
     private final InternalStreamingServiceCallback mCallback;
 
@@ -111,25 +114,25 @@
      */
     public StreamingService(int subscriptionId,
             IMbmsStreamingService service,
+            MbmsStreamingSession session,
             StreamingServiceInfo streamingServiceInfo,
             InternalStreamingServiceCallback callback) {
         mSubscriptionId = subscriptionId;
+        mParentSession = session;
         mService = service;
         mServiceInfo = streamingServiceInfo;
         mCallback = callback;
     }
 
     /**
-     * Retreive the Uri used to play this stream.
+     * Retrieve the Uri used to play this stream.
      *
-     * This may throw a {@link MbmsException} with the error code
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
      *
-     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
-     *
-     * @return The {@link Uri} to pass to the streaming client.
+     * @return The {@link Uri} to pass to the streaming client, or {@code null} if an error
+     *         occurred.
      */
-    public Uri getPlaybackUri() throws MbmsException {
+    public @Nullable Uri getPlaybackUri() {
         if (mService == null) {
             throw new IllegalStateException("No streaming service attached");
         }
@@ -139,25 +142,26 @@
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService = null;
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+            mParentSession.onStreamingServiceStopped(this);
+            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
+            return null;
         }
     }
 
     /**
-     * Retreive the info for this StreamingService.
+     * Retrieve the {@link StreamingServiceInfo} corresponding to this stream.
      */
     public StreamingServiceInfo getInfo() {
         return mServiceInfo;
     }
 
     /**
-     * Stop streaming this service.
-     * This may throw a {@link MbmsException} with the error code
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+     * Stop streaming this service. Further operations on this object will fail with an
+     * {@link IllegalStateException}.
      *
-     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      */
-    public void stopStreaming() throws MbmsException {
+    public void stopStreaming() {
         if (mService == null) {
             throw new IllegalStateException("No streaming service attached");
         }
@@ -167,32 +171,22 @@
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService = null;
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
+        } finally {
+            mParentSession.onStreamingServiceStopped(this);
         }
     }
 
-    /**
-     * Disposes of this stream. Further operations on this object will fail with an
-     * {@link IllegalStateException}.
-     *
-     * This may throw a {@link MbmsException} with the error code
-     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
-     * May also throw an {@link IllegalStateException}
-     */
-    public void dispose() throws MbmsException {
-        if (mService == null) {
-            throw new IllegalStateException("No streaming service attached");
-        }
+    /** @hide */
+    public InternalStreamingServiceCallback getCallback() {
+        return mCallback;
+    }
 
+    private void sendErrorToApp(int errorCode, String message) {
         try {
-            mService.disposeStream(mSubscriptionId, mServiceInfo.getServiceId());
+            mCallback.onError(errorCode, message);
         } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Remote process died");
-            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
-        } catch (IllegalArgumentException e) {
-            throw new IllegalStateException("StreamingService state inconsistent with middleware");
-        } finally {
-            mService = null;
+            // Ignore, should not happen locally.
         }
     }
 }
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
index b72c715..a50a469 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
@@ -16,6 +16,8 @@
 
 package android.telephony.mbms;
 
+import android.annotation.Nullable;
+
 /**
  * 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.
@@ -37,7 +39,7 @@
      * @param errorCode The error code.
      * @param message A human-readable message generated by the middleware for debugging purposes.
      */
-    public void onError(int errorCode, String message) {
+    public void onError(int errorCode, @Nullable String message) {
         // default implementation empty
     }
 
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
index 4dd4292..c90ffc7 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
@@ -17,7 +17,7 @@
 package android.telephony.mbms.vendor;
 
 import android.net.Uri;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IMbmsStreamingSessionCallback;
 import android.telephony.mbms.IStreamingServiceCallback;
 import android.telephony.mbms.StreamingServiceInfo;
 
@@ -26,18 +26,16 @@
  */
 interface IMbmsStreamingService
 {
-    int initialize(IMbmsStreamingManagerCallback listener, int subId);
+    int initialize(IMbmsStreamingSessionCallback callback, int subId);
 
-    int getStreamingServices(int subId, in List<String> serviceClasses);
+    int requestUpdateStreamingServices(int subId, in List<String> serviceClasses);
 
     int startStreaming(int subId, String serviceId,
-            IStreamingServiceCallback listener);
+            IStreamingServiceCallback callback);
 
     Uri getPlaybackUri(int subId, String serviceId);
 
     void stopStreaming(int subId, String serviceId);
 
-    void disposeStream(int subId, String serviceId);
-
     void dispose(int subId);
 }
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 626a681..1a4d0d8 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -22,10 +22,10 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IMbmsStreamingSessionCallback;
 import android.telephony.mbms.IStreamingServiceCallback;
 import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.MbmsStreamingSessionCallback;
 import android.telephony.mbms.StreamingService;
 import android.telephony.mbms.StreamingServiceCallback;
 import android.telephony.mbms.StreamingServiceInfo;
@@ -48,12 +48,12 @@
      *
      * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors}
      * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
-     * {@link IMbmsStreamingManagerCallback#error(int, String)}.
+     * {@link IMbmsStreamingSessionCallback#onError(int, String)}.
      *
      * @param callback The callback to use to communicate with the app.
      * @param subscriptionId The subscription ID to use.
      */
-    public int initialize(MbmsStreamingManagerCallback callback, int subscriptionId)
+    public int initialize(MbmsStreamingSessionCallback callback, int subscriptionId)
             throws RemoteException {
         return 0;
     }
@@ -63,7 +63,7 @@
      * @hide
      */
     @Override
-    public final int initialize(final IMbmsStreamingManagerCallback callback,
+    public final int initialize(final IMbmsStreamingSessionCallback callback,
             final int subscriptionId) throws RemoteException {
         final int uid = Binder.getCallingUid();
         callback.asBinder().linkToDeath(new DeathRecipient() {
@@ -73,11 +73,11 @@
             }
         }, 0);
 
-        return initialize(new MbmsStreamingManagerCallback() {
+        return initialize(new MbmsStreamingSessionCallback() {
             @Override
             public void onError(int errorCode, String message) {
                 try {
-                    callback.error(errorCode, message);
+                    callback.onError(errorCode, message);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -86,7 +86,7 @@
             @Override
             public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
                 try {
-                    callback.streamingServicesUpdated(services);
+                    callback.onStreamingServicesUpdated(services);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -95,7 +95,7 @@
             @Override
             public void onMiddlewareReady() {
                 try {
-                    callback.middlewareReady();
+                    callback.onMiddlewareReady();
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -107,7 +107,7 @@
     /**
      * 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)}
+     * later via {@link IMbmsStreamingSessionCallback#onStreamingServicesUpdated(List)}
      *
      * Note that subsequent calls with the same uid and subId will replace
      * the service class list.
@@ -122,7 +122,7 @@
      * {@link android.telephony.mbms.MbmsException.GeneralErrors}
      */
     @Override
-    public int getStreamingServices(int subscriptionId,
+    public int requestUpdateStreamingServices(int subscriptionId,
             List<String> serviceClasses) throws RemoteException {
         return 0;
     }
@@ -130,7 +130,7 @@
     /**
      * Starts streaming on a particular service. This method may perform asynchronous work. When
      * the middleware is ready to send bits to the frontend, it should inform the app via
-     * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
+     * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
      *
      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
@@ -164,7 +164,7 @@
             @Override
             public void onError(int errorCode, String message) {
                 try {
-                    callback.error(errorCode, message);
+                    callback.onError(errorCode, message);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -174,7 +174,7 @@
             public void onStreamStateUpdated(@StreamingService.StreamingState int state,
                     @StreamingService.StreamingStateChangeReason int reason) {
                 try {
-                    callback.streamStateUpdated(state, reason);
+                    callback.onStreamStateUpdated(state, reason);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -183,7 +183,7 @@
             @Override
             public void onMediaDescriptionUpdated() {
                 try {
-                    callback.mediaDescriptionUpdated();
+                    callback.onMediaDescriptionUpdated();
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -192,7 +192,7 @@
             @Override
             public void onBroadcastSignalStrengthUpdated(int signalStrength) {
                 try {
-                    callback.broadcastSignalStrengthUpdated(signalStrength);
+                    callback.onBroadcastSignalStrengthUpdated(signalStrength);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -201,7 +201,7 @@
             @Override
             public void onStreamMethodUpdated(int methodType) {
                 try {
-                    callback.streamMethodUpdated(methodType);
+                    callback.onStreamMethodUpdated(methodType);
                 } catch (RemoteException e) {
                     onAppCallbackDied(uid, subscriptionId);
                 }
@@ -228,7 +228,11 @@
     /**
      * Stop streaming the stream identified by {@code serviceId}. Notification of the resulting
      * stream state change should be reported to the app via
-     * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
+     * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
+     *
+     * In addition, the callback provided via
+     * {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be
+     * used after this method has called by the app.
      *
      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
@@ -241,27 +245,10 @@
     }
 
     /**
-     * Dispose of the stream identified by {@code serviceId} for the app identified by the
-     * {@code appName} and {@code subscriptionId} arguments along with the caller's uid.
-     * No notification back to the app is required for this operation, and the callback provided via
-     * {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be
-     * used after this method has called by the app.
-     *
-     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
-     *
-     * @param subscriptionId The subscription id to use.
-     * @param serviceId The ID of the streaming service that the app wishes to dispose of.
-     */
-    @Override
-    public void disposeStream(int subscriptionId, String serviceId)
-            throws RemoteException {
-    }
-
-    /**
      * Signals that the app wishes to dispose of the session identified by the
      * {@code subscriptionId} argument and the caller's uid. No notification back to the
      * app is required for this operation, and the corresponding callback provided via
-     * {@link #initialize(IMbmsStreamingManagerCallback, int)} should no longer be used
+     * {@link #initialize(IMbmsStreamingSessionCallback, int)} should no longer be used
      * after this method has been called by the app.
      *
      * May throw an {@link IllegalStateException}
diff --git a/tests/net/java/android/net/ip/IpManagerTest.java b/tests/net/java/android/net/ip/IpManagerTest.java
index 541f91ad..22d88fb 100644
--- a/tests/net/java/android/net/ip/IpManagerTest.java
+++ b/tests/net/java/android/net/ip/IpManagerTest.java
@@ -180,7 +180,8 @@
         // Add N - 1 addresses
         for (int i = 0; i < lastAddr; i++) {
             mObserver.addressUpdated(iface, new LinkAddress(addresses[i]));
-            verify(mCb, timeout(100).times(1)).onLinkPropertiesChange(any());
+            verify(mCb, timeout(100)).onLinkPropertiesChange(any());
+            reset(mCb);
         }
 
         // Add Nth address
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
index 9115378..0a5a6aa 100644
--- a/tests/net/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -60,7 +60,7 @@
 
     NsdManager mManager;
 
-    long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+    long mTimeoutMs = 200; // non-final so that tests can adjust the value.
 
     @Before
     public void setUp() throws Exception {
@@ -74,7 +74,7 @@
 
     @After
     public void tearDown() throws Exception {
-        waitForIdleHandler(mServiceHandler, mTimeoutMs);
+        mServiceHandler.waitForIdle(mTimeoutMs);
         mServiceHandler.chan.disconnect();
         mServiceHandler.stop();
         if (mManager != null) {
@@ -334,9 +334,10 @@
     }
 
     int verifyRequest(int expectedMessageType) {
+        mServiceHandler.waitForIdle(mTimeoutMs);
         verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
         reset(mServiceHandler);
-        Message received = mServiceHandler.lastMessage;
+        Message received = mServiceHandler.getLastMessage();
         assertEquals(NsdManager.nameOf(expectedMessageType), NsdManager.nameOf(received.what));
         return received.arg2;
     }
@@ -347,31 +348,43 @@
 
     // Implements the server side of AsyncChannel connection protocol
     public static class MockServiceHandler extends Handler {
-        public Context mContext;
+        public final Context context;
         public AsyncChannel chan;
-        public volatile Message lastMessage;
+        public Message lastMessage;
 
-        MockServiceHandler(Looper looper, Context context) {
-            super(looper);
-            mContext = context;
+        MockServiceHandler(Looper l, Context c) {
+            super(l);
+            context = c;
+        }
+
+        synchronized Message getLastMessage() {
+            return lastMessage;
+        }
+
+        synchronized void setLastMessage(Message msg) {
+            lastMessage = obtainMessage();
+            lastMessage.copyFrom(msg);
+        }
+
+        void waitForIdle(long timeoutMs) {
+            waitForIdleHandler(this, timeoutMs);
         }
 
         @Override
         public void handleMessage(Message msg) {
-            lastMessage = obtainMessage();
-            lastMessage.copyFrom(msg);
+            setLastMessage(msg);
             if (msg.what == AsyncChannel.CMD_CHANNEL_FULL_CONNECTION) {
                 chan = new AsyncChannel();
-                chan.connect(mContext, this, msg.replyTo);
+                chan.connect(context, this, msg.replyTo);
                 chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
             }
         }
 
-        public void stop() {
+        void stop() {
             getLooper().quitSafely();
         }
 
-        public static MockServiceHandler create(Context context) {
+        static MockServiceHandler create(Context context) {
             HandlerThread t = new HandlerThread("mock-service-handler");
             t.start();
             return new MockServiceHandler(t.getLooper(), context);
diff --git a/tests/net/java/com/android/internal/util/TestUtils.java b/tests/net/java/com/android/internal/util/TestUtils.java
index c9fa340..6db01d3 100644
--- a/tests/net/java/com/android/internal/util/TestUtils.java
+++ b/tests/net/java/com/android/internal/util/TestUtils.java
@@ -30,8 +30,7 @@
      * Block until the given Handler thread becomes idle, or until timeoutMs has passed.
      */
     public static void waitForIdleHandler(HandlerThread handlerThread, long timeoutMs) {
-        // TODO: convert to getThreadHandler once it is available on aosp
-        waitForIdleLooper(handlerThread.getLooper(), timeoutMs);
+        waitForIdleHandler(handlerThread.getThreadHandler(), timeoutMs);
     }
 
     /**
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8816d43..a814738 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -683,7 +683,8 @@
         public WrappedNetworkMonitor(Context context, Handler handler,
                 NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
                 IpConnectivityLog log) {
-            super(context, handler, networkAgentInfo, defaultRequest, log);
+            super(context, handler, networkAgentInfo, defaultRequest, log,
+                    NetworkMonitor.NetworkMonitorSettings.DEFAULT);
         }
 
         @Override
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index eff04ab..f72a1c6 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity;
 
+import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
+import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
 import static com.android.server.connectivity.MetricsTestUtil.aBool;
 import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
 import static com.android.server.connectivity.MetricsTestUtil.aLong;
@@ -31,29 +33,41 @@
 import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ETHERNET;
 import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE;
 import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import android.net.ConnectivityMetricsEvent;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
+import android.net.metrics.ConnectStats;
 import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.DhcpClientEvent;
 import android.net.metrics.DhcpErrorEvent;
 import android.net.metrics.DnsEvent;
+import android.net.metrics.DnsEvent;
 import android.net.metrics.IpManagerEvent;
 import android.net.metrics.IpReachabilityEvent;
 import android.net.metrics.NetworkEvent;
 import android.net.metrics.RaEvent;
 import android.net.metrics.ValidationProbeEvent;
+import android.net.metrics.WakeupStats;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
 import java.util.Arrays;
 import java.util.List;
-import junit.framework.TestCase;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
 // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
-public class IpConnectivityEventBuilderTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConnectivityEventBuilderTest {
 
-    @SmallTest
+    @Test
     public void testLinkLayerInferrence() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(IpReachabilityEvent.class),
@@ -182,7 +196,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testDefaultNetworkEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(DefaultNetworkEvent.class),
@@ -223,7 +237,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testDhcpClientEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(DhcpClientEvent.class),
@@ -249,7 +263,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testDhcpErrorEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(DhcpErrorEvent.class),
@@ -274,7 +288,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testIpManagerEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(IpManagerEvent.class),
@@ -300,7 +314,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testIpReachabilityEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(IpReachabilityEvent.class),
@@ -324,7 +338,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testNetworkEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(NetworkEvent.class),
@@ -353,7 +367,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testValidationProbeEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(ValidationProbeEvent.class),
@@ -380,7 +394,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testApfProgramEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(ApfProgramEvent.class),
@@ -414,7 +428,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testApfStatsSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(ApfStats.class),
@@ -457,7 +471,7 @@
         verifySerialization(want, ev);
     }
 
-    @SmallTest
+    @Test
     public void testRaEventSerialization() {
         ConnectivityMetricsEvent ev = describeIpEvent(
                 aType(RaEvent.class),
@@ -490,11 +504,49 @@
         verifySerialization(want, ev);
     }
 
+    @Test
+    public void testWakeupStatsSerialization() {
+        WakeupStats stats = new WakeupStats("wlan0");
+        stats.totalWakeups = 14;
+        stats.applicationWakeups = 5;
+        stats.nonApplicationWakeups = 1;
+        stats.rootWakeups = 2;
+        stats.systemWakeups = 3;
+        stats.unroutedWakeups = 3;
+
+        IpConnectivityEvent got = IpConnectivityEventBuilder.toProto(stats);
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  wakeup_stats <",
+                "    application_wakeups: 5",
+                "    duration_sec: 0",
+                "    non_application_wakeups: 1",
+                "    root_wakeups: 2",
+                "    system_wakeups: 3",
+                "    total_wakeups: 14",
+                "    unrouted_wakeups: 3",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, got);
+    }
+
     static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
+        List<IpConnectivityEvent> protoInput =
+                IpConnectivityEventBuilder.toProto(Arrays.asList(input));
+        verifySerialization(want, protoInput.toArray(new IpConnectivityEvent[0]));
+    }
+
+    static void verifySerialization(String want, IpConnectivityEvent... input) {
         try {
-            List<IpConnectivityEvent> proto =
-                    IpConnectivityEventBuilder.toProto(Arrays.asList(input));
-            byte[] got = IpConnectivityEventBuilder.serialize(0, proto);
+            byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input));
             IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
             assertEquals(want, log.toString());
         } catch (Exception e) {
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index cc18b7f..ede5988 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -224,6 +224,15 @@
         dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
         dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34);
 
+        // iface, uid
+        wakeupEvent("wlan0", 1000);
+        wakeupEvent("rmnet0", 10123);
+        wakeupEvent("wlan0", 1000);
+        wakeupEvent("rmnet0", 10008);
+        wakeupEvent("wlan0", -1);
+        wakeupEvent("wlan0", 10008);
+        wakeupEvent("rmnet0", 1000);
+
         String want = String.join("\n",
                 "dropped_events: 0",
                 "events <",
@@ -405,6 +414,38 @@
                 "    return_codes: 0",
                 "  >",
                 ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 2",
+                "  network_id: 0",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  wakeup_stats <",
+                "    application_wakeups: 2",
+                "    duration_sec: 0",
+                "    non_application_wakeups: 0",
+                "    root_wakeups: 0",
+                "    system_wakeups: 1",
+                "    total_wakeups: 3",
+                "    unrouted_wakeups: 0",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  wakeup_stats <",
+                "    application_wakeups: 1",
+                "    duration_sec: 0",
+                "    non_application_wakeups: 0",
+                "    root_wakeups: 0",
+                "    system_wakeups: 2",
+                "    total_wakeups: 4",
+                "    unrouted_wakeups: 1",
+                "  >",
+                ">",
                 "version: 2\n");
 
         verifySerialization(want, getdump("flush"));
@@ -425,6 +466,11 @@
         mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
     }
 
+    void wakeupEvent(String iface, int uid) throws Exception {
+        String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+        mNetdListener.onWakeupEvent(prefix, uid, uid, 0);
+    }
+
     List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
         ArgumentCaptor<ConnectivityMetricsEvent> captor =
                 ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 46f395e..2b105e5 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -19,6 +19,7 @@
 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
@@ -37,9 +38,11 @@
 import android.system.OsConstants;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
+
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.DNSLookupBatch;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -47,6 +50,7 @@
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -75,6 +79,118 @@
     }
 
     @Test
+    public void testWakeupEventLogging() throws Exception {
+        final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH;
+
+        // Assert no events
+        String[] events1 = listNetdEvent();
+        assertEquals(new String[]{""}, events1);
+
+        long now = System.currentTimeMillis();
+        String prefix = "iface:wlan0";
+        int[] uids = { 10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004 };
+        for (int uid : uids) {
+            mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
+        }
+
+        String[] events2 = listNetdEvent();
+        int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
+        assertEquals(expectedLength2, events2.length);
+        assertContains(events2[0], "WakeupStats");
+        assertContains(events2[0], "wlan0");
+        for (int i = 0; i < uids.length; i++) {
+            String got = events2[i+1];
+            assertContains(got, "WakeupEvent");
+            assertContains(got, "wlan0");
+            assertContains(got, "uid: " + uids[i]);
+        }
+
+        int uid = 20000;
+        for (int i = 0; i < BUFFER_LENGTH * 2; i++) {
+            long ts = now + 10;
+            mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts);
+        }
+
+        String[] events3 = listNetdEvent();
+        int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
+        assertEquals(expectedLength3, events3.length);
+        assertContains(events2[0], "WakeupStats");
+        assertContains(events2[0], "wlan0");
+        for (int i = 1; i < expectedLength3; i++) {
+            String got = events3[i];
+            assertContains(got, "WakeupEvent");
+            assertContains(got, "wlan0");
+            assertContains(got, "uid: " + uid);
+        }
+
+        uid = 45678;
+        mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
+
+        String[] events4 = listNetdEvent();
+        String lastEvent = events4[events4.length - 1];
+        assertContains(lastEvent, "WakeupEvent");
+        assertContains(lastEvent, "wlan0");
+        assertContains(lastEvent, "uid: " + uid);
+    }
+
+    @Test
+    public void testWakeupStatsLogging() throws Exception {
+        wakeupEvent("wlan0", 1000);
+        wakeupEvent("rmnet0", 10123);
+        wakeupEvent("wlan0", 1000);
+        wakeupEvent("rmnet0", 10008);
+        wakeupEvent("wlan0", -1);
+        wakeupEvent("wlan0", 10008);
+        wakeupEvent("rmnet0", 1000);
+        wakeupEvent("wlan0", 10004);
+        wakeupEvent("wlan0", 1000);
+        wakeupEvent("wlan0", 0);
+        wakeupEvent("wlan0", -1);
+        wakeupEvent("rmnet0", 10052);
+        wakeupEvent("wlan0", 0);
+        wakeupEvent("rmnet0", 1000);
+        wakeupEvent("wlan0", 1010);
+
+        String got = flushStatistics();
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 2",
+                "  network_id: 0",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  wakeup_stats <",
+                "    application_wakeups: 3",
+                "    duration_sec: 0",
+                "    non_application_wakeups: 0",
+                "    root_wakeups: 0",
+                "    system_wakeups: 2",
+                "    total_wakeups: 5",
+                "    unrouted_wakeups: 0",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  wakeup_stats <",
+                "    application_wakeups: 2",
+                "    duration_sec: 0",
+                "    non_application_wakeups: 1",
+                "    root_wakeups: 2",
+                "    system_wakeups: 3",
+                "    total_wakeups: 10",
+                "    unrouted_wakeups: 2",
+                "  >",
+                ">",
+                "version: 2\n");
+        assertEquals(want, got);
+    }
+
+    @Test
     public void testDnsLogging() throws Exception {
         asyncDump(100);
 
@@ -297,6 +413,11 @@
         mNetdEventListenerService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
     }
 
+    void wakeupEvent(String iface, int uid) throws Exception {
+        String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+        mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, 0);
+    }
+
     void asyncDump(long durationMs) throws Exception {
         final long stop = System.currentTimeMillis() + durationMs;
         final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
@@ -329,4 +450,15 @@
         }
         return log.toString();
     }
+
+    String[] listNetdEvent() throws Exception {
+        StringWriter buffer = new StringWriter();
+        PrintWriter writer = new PrintWriter(buffer);
+        mNetdEventListenerService.list(writer);
+        return buffer.toString().split("\\n");
+    }
+
+    static void assertContains(String got, String want) {
+        assertTrue(got + " did not contain \"" + want + "\"", got.contains(want));
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
new file mode 100644
index 0000000..27a897d
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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.connectivity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.metrics.IpConnectivityLog;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.TelephonyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkMonitorTest {
+
+    static final int TEST_ID = 60; // should be less than min netid 100
+
+    @Mock Context mContext;
+    @Mock Handler mHandler;
+    @Mock IpConnectivityLog mLogger;
+    @Mock NetworkAgentInfo mAgent;
+    @Mock NetworkMonitor.NetworkMonitorSettings mSettings;
+    @Mock NetworkRequest mRequest;
+    @Mock TelephonyManager mTelephony;
+    @Mock WifiManager mWifi;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mAgent.network()).thenReturn(new Network(TEST_ID));
+        when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
+        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
+    }
+
+    NetworkMonitor makeMonitor() {
+        return new NetworkMonitor(mContext, mHandler, mAgent, mRequest, mLogger, mSettings);
+    }
+
+    @Test
+    public void testCreatingNetworkMonitor() {
+        NetworkMonitor monitor = makeMonitor();
+    }
+}
+