Merge "Notify the user and turn off tethering when the service is disallowed."
diff --git a/Android.mk b/Android.mk
index 5f759cb..28ff5c1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -221,6 +221,7 @@
 	core/java/android/net/INetworkScoreService.aidl \
 	core/java/android/net/INetworkStatsService.aidl \
 	core/java/android/net/INetworkStatsSession.aidl \
+	core/java/android/net/ITetheringStatsProvider.aidl \
 	core/java/android/net/nsd/INsdManager.aidl \
 	core/java/android/nfc/IAppCallback.aidl \
 	core/java/android/nfc/INfcAdapter.aidl \
@@ -229,6 +230,7 @@
 	core/java/android/nfc/INfcCardEmulation.aidl \
 	core/java/android/nfc/INfcFCardEmulation.aidl \
 	core/java/android/nfc/INfcUnlockHandler.aidl \
+	core/java/android/nfc/INfcDta.aidl \
 	core/java/android/nfc/ITagRemovedCallback.aidl \
 	core/java/android/os/IBatteryPropertiesListener.aidl \
 	core/java/android/os/IBatteryPropertiesRegistrar.aidl \
@@ -436,7 +438,7 @@
 	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/IDownloadCallback.aidl \
+	telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl \
         telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl \
 	telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl \
 	telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl \
@@ -543,6 +545,32 @@
 
 framework_built := $(call java-lib-deps,framework)
 
+# HwBinder
+# =======================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+        core/java/android/os/HidlSupport.java \
+        core/java/android/annotation/NonNull.java \
+        core/java/android/os/HwBinder.java \
+        core/java/android/os/HwBlob.java \
+        core/java/android/os/HwParcel.java \
+        core/java/android/os/IHwBinder.java \
+        core/java/android/os/IHwInterface.java \
+        core/java/android/os/DeadObjectException.java \
+        core/java/android/os/DeadSystemException.java \
+        core/java/android/os/RemoteException.java \
+        core/java/android/util/AndroidException.java \
+
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := hwbinder
+
+LOCAL_DX_FLAGS := --core-library
+LOCAL_UNINSTALLABLE_MODULE := true
+include $(BUILD_JAVA_LIBRARY)
+
 # Copy AIDL files to be preprocessed and included in the SDK,
 # specified relative to the root of the build tree.
 # ============================================================
@@ -550,7 +578,6 @@
 
 aidl_files := \
         frameworks/base/telephony/java/android/telephony/mbms/DownloadRequest.aidl \
-        frameworks/base/telephony/java/android/telephony/mbms/DownloadStatus.aidl \
         frameworks/base/telephony/java/android/telephony/mbms/FileInfo.aidl \
         frameworks/base/telephony/java/android/telephony/mbms/FileServiceInfo.aidl \
         frameworks/base/telephony/java/android/telephony/mbms/ServiceInfo.aidl \
diff --git a/api/current.txt b/api/current.txt
index 4275e9d..739e7f2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -114,6 +114,7 @@
     field public static final java.lang.String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field public static final java.lang.String SEND_EMBMS_INTENTS = "android.permission.SEND_EMBMS_INTENTS";
     field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
     field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -24008,6 +24009,7 @@
     field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
     field public static final int TRANSPORT_CELLULAR = 0; // 0x0
     field public static final int TRANSPORT_ETHERNET = 3; // 0x3
+    field public static final int TRANSPORT_LOWPAN = 6; // 0x6
     field public static final int TRANSPORT_VPN = 4; // 0x4
     field public static final int TRANSPORT_WIFI = 1; // 0x1
     field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5
@@ -36341,6 +36343,7 @@
     field public static final int FD_CLOEXEC;
     field public static final int FIONREAD;
     field public static final int F_DUPFD;
+    field public static final int F_DUPFD_CLOEXEC;
     field public static final int F_GETFD;
     field public static final int F_GETFL;
     field public static final int F_GETLK;
@@ -36435,7 +36438,9 @@
     field public static final int NI_NUMERICSERV;
     field public static final int O_ACCMODE;
     field public static final int O_APPEND;
+    field public static final int O_CLOEXEC;
     field public static final int O_CREAT;
+    field public static final int O_DSYNC;
     field public static final int O_EXCL;
     field public static final int O_NOCTTY;
     field public static final int O_NOFOLLOW;
@@ -36681,14 +36686,18 @@
 
   public final class StructStat {
     ctor public StructStat(long, long, int, long, int, int, long, long, long, long, long, long, long);
+    ctor public StructStat(long, long, int, long, int, int, long, long, android.system.StructTimespec, android.system.StructTimespec, android.system.StructTimespec, long, long);
+    field public final android.system.StructTimespec st_atim;
     field public final long st_atime;
     field public final long st_blksize;
     field public final long st_blocks;
+    field public final android.system.StructTimespec st_ctim;
     field public final long st_ctime;
     field public final long st_dev;
     field public final int st_gid;
     field public final long st_ino;
     field public final int st_mode;
+    field public final android.system.StructTimespec st_mtim;
     field public final long st_mtime;
     field public final long st_nlink;
     field public final long st_rdev;
@@ -36711,6 +36720,13 @@
     field public final long f_namemax;
   }
 
+  public final class StructTimespec implements java.lang.Comparable {
+    ctor public StructTimespec(long, long);
+    method public int compareTo(android.system.StructTimespec);
+    field public final long tv_nsec;
+    field public final long tv_sec;
+  }
+
   public final class StructUtsname {
     ctor public StructUtsname(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
     field public final java.lang.String machine;
@@ -37805,6 +37821,14 @@
     field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4
   }
 
+  public class MbmsStreamingManager {
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int) throws android.telephony.mbms.MbmsException;
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback) throws android.telephony.mbms.MbmsException;
+    method public void dispose();
+    method public void getStreamingServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback) throws android.telephony.mbms.MbmsException;
+  }
+
   public class NeighboringCellInfo implements android.os.Parcelable {
     ctor public deprecated NeighboringCellInfo();
     ctor public deprecated NeighboringCellInfo(int, int);
@@ -38417,6 +38441,95 @@
 
 }
 
+package android.telephony.mbms {
+
+  public class MbmsException extends java.lang.Exception {
+    method public int getErrorCode();
+    field public static final int ERROR_MIDDLEWARE_LOST = 3; // 0x3
+    field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2
+    field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public static class MbmsException.GeneralErrors {
+    ctor public MbmsException.GeneralErrors();
+    field public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; // 0xcf
+    field public static final int ERROR_IN_E911 = 204; // 0xcc
+    field public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; // 0xc9
+    field public static final int ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE = 203; // 0xcb
+    field public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 205; // 0xcd
+    field public static final int ERROR_OUT_OF_MEMORY = 202; // 0xca
+    field public static final int ERROR_UNABLE_TO_READ_SIM = 206; // 0xce
+  }
+
+  public static class MbmsException.InitializationErrors {
+    ctor public MbmsException.InitializationErrors();
+    field public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; // 0x66
+    field public static final int ERROR_DUPLICATE_INITIALIZE = 101; // 0x65
+    field public static final int ERROR_UNABLE_TO_INITIALIZE = 103; // 0x67
+  }
+
+  public static class MbmsException.StreamingErrors {
+    ctor public MbmsException.StreamingErrors();
+    field public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; // 0x12d
+    field public static final int ERROR_DUPLICATE_START_STREAM = 303; // 0x12f
+    field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
+  }
+
+  public class MbmsStreamingManagerCallback extends android.os.Binder {
+    ctor public MbmsStreamingManagerCallback();
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void middlewareReady() throws android.os.RemoteException;
+    method public void streamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>) throws android.os.RemoteException;
+  }
+
+  public class ServiceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getClassName();
+    method public java.util.List<java.util.Locale> getLocales();
+    method public java.util.Map<java.util.Locale, java.lang.String> getNames();
+    method public java.lang.String getServiceId();
+    method public java.util.Date getSessionEndTime();
+    method public java.util.Date getSessionStartTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.ServiceInfo> CREATOR;
+  }
+
+  public class StreamingService {
+    method public void dispose() throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingServiceInfo getInfo();
+    method public android.net.Uri getPlaybackUri() throws android.telephony.mbms.MbmsException;
+    method public void stopStreaming() throws android.telephony.mbms.MbmsException;
+    field public static final int BROADCAST_METHOD = 1; // 0x1
+    field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
+    field public static final int REASON_END_OF_SESSION = 2; // 0x2
+    field public static final int REASON_FREQUENCY_CONFLICT = 3; // 0x3
+    field public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 5; // 0x5
+    field public static final int REASON_NONE = 0; // 0x0
+    field public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; // 0x5
+    field public static final int REASON_OUT_OF_MEMORY = 4; // 0x4
+    field public static final int STATE_STALLED = 3; // 0x3
+    field public static final int STATE_STARTED = 2; // 0x2
+    field public static final int STATE_STOPPED = 1; // 0x1
+    field public static final int UNICAST_METHOD = 2; // 0x2
+  }
+
+  public class StreamingServiceCallback extends android.os.Binder {
+    ctor public StreamingServiceCallback();
+    method public void broadcastSignalStrengthUpdated(int) throws android.os.RemoteException;
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void mediaDescriptionUpdated() throws android.os.RemoteException;
+    method public void streamMethodUpdated(int) throws android.os.RemoteException;
+    method public void streamStateUpdated(int, int) throws android.os.RemoteException;
+    field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff
+  }
+
+  public class StreamingServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR;
+  }
+
+}
+
 package android.test {
 
   public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
@@ -49722,6 +49835,7 @@
   }
 
   public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
+    ctor public InMemoryDexClassLoader(java.nio.ByteBuffer[], java.lang.ClassLoader);
     ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader);
   }
 
@@ -69072,6 +69186,8 @@
 
   public class JSONException extends java.lang.Exception {
     ctor public JSONException(java.lang.String);
+    ctor public JSONException(java.lang.String, java.lang.Throwable);
+    ctor public JSONException(java.lang.Throwable);
   }
 
   public class JSONObject {
diff --git a/api/system-current.txt b/api/system-current.txt
index f33d506..0aa5d58 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -206,6 +206,7 @@
     field public static final java.lang.String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
     field public static final java.lang.String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
     field public static final java.lang.String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
+    field public static final java.lang.String SEND_EMBMS_INTENTS = "android.permission.SEND_EMBMS_INTENTS";
     field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
     field public static final java.lang.String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -25846,6 +25847,7 @@
     field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
     field public static final int TRANSPORT_CELLULAR = 0; // 0x0
     field public static final int TRANSPORT_ETHERNET = 3; // 0x3
+    field public static final int TRANSPORT_LOWPAN = 6; // 0x6
     field public static final int TRANSPORT_VPN = 4; // 0x4
     field public static final int TRANSPORT_WIFI = 1; // 0x1
     field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5
@@ -39292,6 +39294,7 @@
     field public static final int FD_CLOEXEC;
     field public static final int FIONREAD;
     field public static final int F_DUPFD;
+    field public static final int F_DUPFD_CLOEXEC;
     field public static final int F_GETFD;
     field public static final int F_GETFL;
     field public static final int F_GETLK;
@@ -39386,7 +39389,9 @@
     field public static final int NI_NUMERICSERV;
     field public static final int O_ACCMODE;
     field public static final int O_APPEND;
+    field public static final int O_CLOEXEC;
     field public static final int O_CREAT;
+    field public static final int O_DSYNC;
     field public static final int O_EXCL;
     field public static final int O_NOCTTY;
     field public static final int O_NOFOLLOW;
@@ -39632,14 +39637,18 @@
 
   public final class StructStat {
     ctor public StructStat(long, long, int, long, int, int, long, long, long, long, long, long, long);
+    ctor public StructStat(long, long, int, long, int, int, long, long, android.system.StructTimespec, android.system.StructTimespec, android.system.StructTimespec, long, long);
+    field public final android.system.StructTimespec st_atim;
     field public final long st_atime;
     field public final long st_blksize;
     field public final long st_blocks;
+    field public final android.system.StructTimespec st_ctim;
     field public final long st_ctime;
     field public final long st_dev;
     field public final int st_gid;
     field public final long st_ino;
     field public final int st_mode;
+    field public final android.system.StructTimespec st_mtim;
     field public final long st_mtime;
     field public final long st_nlink;
     field public final long st_rdev;
@@ -39662,6 +39671,13 @@
     field public final long f_namemax;
   }
 
+  public final class StructTimespec implements java.lang.Comparable {
+    ctor public StructTimespec(long, long);
+    method public int compareTo(android.system.StructTimespec);
+    field public final long tv_nsec;
+    field public final long tv_sec;
+  }
+
   public final class StructUtsname {
     ctor public StructUtsname(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
     field public final java.lang.String machine;
@@ -40976,6 +40992,15 @@
     field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4
   }
 
+  public class MbmsStreamingManager {
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int) throws android.telephony.mbms.MbmsException;
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback) throws android.telephony.mbms.MbmsException;
+    method public void dispose();
+    method public void getStreamingServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback) throws android.telephony.mbms.MbmsException;
+    field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
+  }
+
   public class NeighboringCellInfo implements android.os.Parcelable {
     ctor public deprecated NeighboringCellInfo();
     ctor public deprecated NeighboringCellInfo(int, int);
@@ -41671,6 +41696,111 @@
 
 }
 
+package android.telephony.mbms {
+
+  public class MbmsException extends java.lang.Exception {
+    method public int getErrorCode();
+    field public static final int ERROR_MIDDLEWARE_LOST = 3; // 0x3
+    field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2
+    field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public static class MbmsException.GeneralErrors {
+    ctor public MbmsException.GeneralErrors();
+    field public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; // 0xcf
+    field public static final int ERROR_IN_E911 = 204; // 0xcc
+    field public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; // 0xc9
+    field public static final int ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE = 203; // 0xcb
+    field public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 205; // 0xcd
+    field public static final int ERROR_OUT_OF_MEMORY = 202; // 0xca
+    field public static final int ERROR_UNABLE_TO_READ_SIM = 206; // 0xce
+  }
+
+  public static class MbmsException.InitializationErrors {
+    ctor public MbmsException.InitializationErrors();
+    field public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; // 0x66
+    field public static final int ERROR_DUPLICATE_INITIALIZE = 101; // 0x65
+    field public static final int ERROR_UNABLE_TO_INITIALIZE = 103; // 0x67
+  }
+
+  public static class MbmsException.StreamingErrors {
+    ctor public MbmsException.StreamingErrors();
+    field public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; // 0x12d
+    field public static final int ERROR_DUPLICATE_START_STREAM = 303; // 0x12f
+    field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
+  }
+
+  public class MbmsStreamingManagerCallback extends android.os.Binder {
+    ctor public MbmsStreamingManagerCallback();
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void middlewareReady() throws android.os.RemoteException;
+    method public void streamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>) throws android.os.RemoteException;
+  }
+
+  public class ServiceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getClassName();
+    method public java.util.List<java.util.Locale> getLocales();
+    method public java.util.Map<java.util.Locale, java.lang.String> getNames();
+    method public java.lang.String getServiceId();
+    method public java.util.Date getSessionEndTime();
+    method public java.util.Date getSessionStartTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.ServiceInfo> CREATOR;
+  }
+
+  public class StreamingService {
+    method public void dispose() throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingServiceInfo getInfo();
+    method public android.net.Uri getPlaybackUri() throws android.telephony.mbms.MbmsException;
+    method public void stopStreaming() throws android.telephony.mbms.MbmsException;
+    field public static final int BROADCAST_METHOD = 1; // 0x1
+    field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
+    field public static final int REASON_END_OF_SESSION = 2; // 0x2
+    field public static final int REASON_FREQUENCY_CONFLICT = 3; // 0x3
+    field public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 5; // 0x5
+    field public static final int REASON_NONE = 0; // 0x0
+    field public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; // 0x5
+    field public static final int REASON_OUT_OF_MEMORY = 4; // 0x4
+    field public static final int STATE_STALLED = 3; // 0x3
+    field public static final int STATE_STARTED = 2; // 0x2
+    field public static final int STATE_STOPPED = 1; // 0x1
+    field public static final int UNICAST_METHOD = 2; // 0x2
+  }
+
+  public class StreamingServiceCallback extends android.os.Binder {
+    ctor public StreamingServiceCallback();
+    method public void broadcastSignalStrengthUpdated(int) throws android.os.RemoteException;
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void mediaDescriptionUpdated() throws android.os.RemoteException;
+    method public void streamMethodUpdated(int) throws android.os.RemoteException;
+    method public void streamStateUpdated(int, int) throws android.os.RemoteException;
+    field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff
+  }
+
+  public class StreamingServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+    ctor public StreamingServiceInfo(java.util.Map<java.util.Locale, java.lang.String>, java.lang.String, java.util.List<java.util.Locale>, java.lang.String, java.util.Date, java.util.Date);
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR;
+  }
+
+}
+
+package android.telephony.mbms.vendor {
+
+  public class MbmsStreamingServiceBase extends android.os.Binder {
+    ctor public MbmsStreamingServiceBase();
+    method public void dispose(int) throws android.os.RemoteException;
+    method public void disposeStream(int, java.lang.String) throws android.os.RemoteException;
+    method public android.net.Uri getPlaybackUri(int, java.lang.String) throws android.os.RemoteException;
+    method public int getStreamingServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
+    method public int initialize(android.telephony.mbms.MbmsStreamingManagerCallback, int) throws android.os.RemoteException;
+    method public int startStreaming(int, java.lang.String, android.telephony.mbms.StreamingServiceCallback) throws android.os.RemoteException;
+    method public void stopStreaming(int, java.lang.String) throws android.os.RemoteException;
+  }
+
+}
+
 package android.test {
 
   public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
@@ -53345,6 +53475,7 @@
   }
 
   public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
+    ctor public InMemoryDexClassLoader(java.nio.ByteBuffer[], java.lang.ClassLoader);
     ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader);
   }
 
@@ -72695,6 +72826,8 @@
 
   public class JSONException extends java.lang.Exception {
     ctor public JSONException(java.lang.String);
+    ctor public JSONException(java.lang.String, java.lang.Throwable);
+    ctor public JSONException(java.lang.Throwable);
   }
 
   public class JSONObject {
diff --git a/api/test-current.txt b/api/test-current.txt
index bd96efb..93c9768 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -114,6 +114,7 @@
     field public static final java.lang.String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final java.lang.String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field public static final java.lang.String SEND_EMBMS_INTENTS = "android.permission.SEND_EMBMS_INTENTS";
     field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
     field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -24082,6 +24083,7 @@
     field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
     field public static final int TRANSPORT_CELLULAR = 0; // 0x0
     field public static final int TRANSPORT_ETHERNET = 3; // 0x3
+    field public static final int TRANSPORT_LOWPAN = 6; // 0x6
     field public static final int TRANSPORT_VPN = 4; // 0x4
     field public static final int TRANSPORT_WIFI = 1; // 0x1
     field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5
@@ -36424,6 +36426,7 @@
     field public static final int FD_CLOEXEC;
     field public static final int FIONREAD;
     field public static final int F_DUPFD;
+    field public static final int F_DUPFD_CLOEXEC;
     field public static final int F_GETFD;
     field public static final int F_GETFL;
     field public static final int F_GETLK;
@@ -36518,7 +36521,9 @@
     field public static final int NI_NUMERICSERV;
     field public static final int O_ACCMODE;
     field public static final int O_APPEND;
+    field public static final int O_CLOEXEC;
     field public static final int O_CREAT;
+    field public static final int O_DSYNC;
     field public static final int O_EXCL;
     field public static final int O_NOCTTY;
     field public static final int O_NOFOLLOW;
@@ -36764,14 +36769,18 @@
 
   public final class StructStat {
     ctor public StructStat(long, long, int, long, int, int, long, long, long, long, long, long, long);
+    ctor public StructStat(long, long, int, long, int, int, long, long, android.system.StructTimespec, android.system.StructTimespec, android.system.StructTimespec, long, long);
+    field public final android.system.StructTimespec st_atim;
     field public final long st_atime;
     field public final long st_blksize;
     field public final long st_blocks;
+    field public final android.system.StructTimespec st_ctim;
     field public final long st_ctime;
     field public final long st_dev;
     field public final int st_gid;
     field public final long st_ino;
     field public final int st_mode;
+    field public final android.system.StructTimespec st_mtim;
     field public final long st_mtime;
     field public final long st_nlink;
     field public final long st_rdev;
@@ -36794,6 +36803,13 @@
     field public final long f_namemax;
   }
 
+  public final class StructTimespec implements java.lang.Comparable {
+    ctor public StructTimespec(long, long);
+    method public int compareTo(android.system.StructTimespec);
+    field public final long tv_nsec;
+    field public final long tv_sec;
+  }
+
   public final class StructUtsname {
     ctor public StructUtsname(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String);
     field public final java.lang.String machine;
@@ -37904,6 +37920,14 @@
     field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4
   }
 
+  public class MbmsStreamingManager {
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int) throws android.telephony.mbms.MbmsException;
+    method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback) throws android.telephony.mbms.MbmsException;
+    method public void dispose();
+    method public void getStreamingServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback) throws android.telephony.mbms.MbmsException;
+  }
+
   public class NeighboringCellInfo implements android.os.Parcelable {
     ctor public deprecated NeighboringCellInfo();
     ctor public deprecated NeighboringCellInfo(int, int);
@@ -38516,6 +38540,95 @@
 
 }
 
+package android.telephony.mbms {
+
+  public class MbmsException extends java.lang.Exception {
+    method public int getErrorCode();
+    field public static final int ERROR_MIDDLEWARE_LOST = 3; // 0x3
+    field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2
+    field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public static class MbmsException.GeneralErrors {
+    ctor public MbmsException.GeneralErrors();
+    field public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; // 0xcf
+    field public static final int ERROR_IN_E911 = 204; // 0xcc
+    field public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; // 0xc9
+    field public static final int ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE = 203; // 0xcb
+    field public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 205; // 0xcd
+    field public static final int ERROR_OUT_OF_MEMORY = 202; // 0xca
+    field public static final int ERROR_UNABLE_TO_READ_SIM = 206; // 0xce
+  }
+
+  public static class MbmsException.InitializationErrors {
+    ctor public MbmsException.InitializationErrors();
+    field public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; // 0x66
+    field public static final int ERROR_DUPLICATE_INITIALIZE = 101; // 0x65
+    field public static final int ERROR_UNABLE_TO_INITIALIZE = 103; // 0x67
+  }
+
+  public static class MbmsException.StreamingErrors {
+    ctor public MbmsException.StreamingErrors();
+    field public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; // 0x12d
+    field public static final int ERROR_DUPLICATE_START_STREAM = 303; // 0x12f
+    field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
+  }
+
+  public class MbmsStreamingManagerCallback extends android.os.Binder {
+    ctor public MbmsStreamingManagerCallback();
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void middlewareReady() throws android.os.RemoteException;
+    method public void streamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>) throws android.os.RemoteException;
+  }
+
+  public class ServiceInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getClassName();
+    method public java.util.List<java.util.Locale> getLocales();
+    method public java.util.Map<java.util.Locale, java.lang.String> getNames();
+    method public java.lang.String getServiceId();
+    method public java.util.Date getSessionEndTime();
+    method public java.util.Date getSessionStartTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.ServiceInfo> CREATOR;
+  }
+
+  public class StreamingService {
+    method public void dispose() throws android.telephony.mbms.MbmsException;
+    method public android.telephony.mbms.StreamingServiceInfo getInfo();
+    method public android.net.Uri getPlaybackUri() throws android.telephony.mbms.MbmsException;
+    method public void stopStreaming() throws android.telephony.mbms.MbmsException;
+    field public static final int BROADCAST_METHOD = 1; // 0x1
+    field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
+    field public static final int REASON_END_OF_SESSION = 2; // 0x2
+    field public static final int REASON_FREQUENCY_CONFLICT = 3; // 0x3
+    field public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 5; // 0x5
+    field public static final int REASON_NONE = 0; // 0x0
+    field public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; // 0x5
+    field public static final int REASON_OUT_OF_MEMORY = 4; // 0x4
+    field public static final int STATE_STALLED = 3; // 0x3
+    field public static final int STATE_STARTED = 2; // 0x2
+    field public static final int STATE_STOPPED = 1; // 0x1
+    field public static final int UNICAST_METHOD = 2; // 0x2
+  }
+
+  public class StreamingServiceCallback extends android.os.Binder {
+    ctor public StreamingServiceCallback();
+    method public void broadcastSignalStrengthUpdated(int) throws android.os.RemoteException;
+    method public void error(int, java.lang.String) throws android.os.RemoteException;
+    method public void mediaDescriptionUpdated() throws android.os.RemoteException;
+    method public void streamMethodUpdated(int) throws android.os.RemoteException;
+    method public void streamStateUpdated(int, int) throws android.os.RemoteException;
+    field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff
+  }
+
+  public class StreamingServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+    field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR;
+  }
+
+}
+
 package android.test {
 
   public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
@@ -49831,6 +49944,7 @@
   }
 
   public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
+    ctor public InMemoryDexClassLoader(java.nio.ByteBuffer[], java.lang.ClassLoader);
     ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader);
   }
 
@@ -69181,6 +69295,8 @@
 
   public class JSONException extends java.lang.Exception {
     ctor public JSONException(java.lang.String);
+    ctor public JSONException(java.lang.String, java.lang.Throwable);
+    ctor public JSONException(java.lang.Throwable);
   }
 
   public class JSONObject {
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 0ea141c..752c2a8 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -233,6 +233,9 @@
     for (i = 0; i < argc; i++) {
         if (known_command == true) {
           runtime.addOption(strdup(argv[i]));
+          // The static analyzer gets upset that we don't ever free the above
+          // string. Since the allocation is from main, leaking it doesn't seem
+          // problematic. NOLINTNEXTLINE
           ALOGV("app_process main add known option '%s'", argv[i]);
           known_command = false;
           continue;
@@ -256,6 +259,9 @@
         }
 
         runtime.addOption(strdup(argv[i]));
+        // The static analyzer gets upset that we don't ever free the above
+        // string. Since the allocation is from main, leaking it doesn't seem
+        // problematic. NOLINTNEXTLINE
         ALOGV("app_process main add option '%s'", argv[i]);
     }
 
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 1ea33ce..f9458a7 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -31,8 +31,8 @@
 #include <android_os_MessageQueue.h>
 #include <core_jni_helpers.h>
 #include <jni.h>
-#include <JNIHelp.h>
-#include <ScopedPrimitiveArray.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <ScopedUtfChars.h>
 #include <utils/Log.h>
 #include <utils/Looper.h>
diff --git a/core/java/android/app/timezone/Callback.java b/core/java/android/app/timezone/Callback.java
index b51e5ba..aea8038 100644
--- a/core/java/android/app/timezone/Callback.java
+++ b/core/java/android/app/timezone/Callback.java
@@ -27,7 +27,6 @@
  *
  * @hide
  */
-// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
 public abstract class Callback {
 
     @Retention(RetentionPolicy.SOURCE)
diff --git a/core/java/android/app/timezone/DistroFormatVersion.java b/core/java/android/app/timezone/DistroFormatVersion.java
index e879e8f..be732e4 100644
--- a/core/java/android/app/timezone/DistroFormatVersion.java
+++ b/core/java/android/app/timezone/DistroFormatVersion.java
@@ -35,7 +35,6 @@
  *
  * @hide
  */
-// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
 public final class DistroFormatVersion implements Parcelable {
 
     private final int mMajorVersion;
diff --git a/core/java/android/app/timezone/DistroRulesVersion.java b/core/java/android/app/timezone/DistroRulesVersion.java
index 5503ce1..a680594 100644
--- a/core/java/android/app/timezone/DistroRulesVersion.java
+++ b/core/java/android/app/timezone/DistroRulesVersion.java
@@ -36,7 +36,6 @@
  *
  * @hide
  */
-// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
 public final class DistroRulesVersion implements Parcelable {
 
     private final String mRulesVersion;
@@ -125,4 +124,8 @@
                 + ", mRevision='" + mRevision + '\''
                 + '}';
     }
+
+    public String toDumpString() {
+        return mRulesVersion + "," + mRevision;
+    }
 }
diff --git a/core/java/android/app/timezone/RulesManager.java b/core/java/android/app/timezone/RulesManager.java
index 649d894..ad9b698 100644
--- a/core/java/android/app/timezone/RulesManager.java
+++ b/core/java/android/app/timezone/RulesManager.java
@@ -64,7 +64,6 @@
  * {@link Context#TIME_ZONE_RULES_MANAGER_SERVICE}.
  * @hide
  */
-// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
 public final class RulesManager {
     private static final String TAG = "timezone.RulesManager";
     private static final boolean DEBUG = false;
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
index 7d6ad21..ec247eb 100644
--- a/core/java/android/app/timezone/RulesState.java
+++ b/core/java/android/app/timezone/RulesState.java
@@ -60,7 +60,6 @@
  *
  * @hide
  */
-// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
 public final class RulesState implements Parcelable {
 
     @Retention(RetentionPolicy.SOURCE)
diff --git a/core/java/android/app/timezone/RulesUpdaterContract.java b/core/java/android/app/timezone/RulesUpdaterContract.java
index 4e77818..9c62f46 100644
--- a/core/java/android/app/timezone/RulesUpdaterContract.java
+++ b/core/java/android/app/timezone/RulesUpdaterContract.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
 
 /**
  * Constants related to the contract between the Android system and the privileged time zone updater
@@ -26,7 +27,6 @@
  *
  * @hide
  */
-// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
 public final class RulesUpdaterContract {
 
     /**
@@ -82,6 +82,9 @@
             byte[] checkTokenBytes) {
         Intent intent = createUpdaterIntent(updaterAppPackageName);
         intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes);
-        context.sendBroadcast(intent, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
+        context.sendBroadcastAsUser(
+                intent,
+                UserHandle.of(UserHandle.myUserId()),
+                RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 6bc88b0..78d7c54 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -203,6 +203,34 @@
             "android.bluetooth.device.action.BOND_STATE_CHANGED";
 
     /**
+     * Broadcast Action: Indicates the battery level of a remote device has
+     * been retrieved for the first time, or changed since the last retrieval
+     * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
+     * #EXTRA_BATTERY_LEVEL}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_BATTERY_LEVEL_CHANGED =
+            "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED";
+
+    /**
+     * Used as an Integer extra field in {@link #ACTION_BATTERY_LEVEL_CHANGED}
+     * intent. It contains the most recently retrieved battery level information
+     * ranging from 0% to 100% for a remote device, {@link #BATTERY_LEVEL_UNKNOWN}
+     * when the valid is unknown or there is an error
+     * @hide
+     */
+    public static final String EXTRA_BATTERY_LEVEL =
+            "android.bluetooth.device.extra.BATTERY_LEVEL";
+
+    /**
+     * Used as the unknown value for {@link #EXTRA_BATTERY_LEVEL} and {@link #getBatteryLevel()}
+     * @hide
+     */
+    public static final int BATTERY_LEVEL_UNKNOWN = -1;
+
+    /**
      * Used as a Parcelable {@link BluetoothDevice} extra field in every intent
      * broadcast by this class. It contains the {@link BluetoothDevice} that
      * the intent applies to.
@@ -864,6 +892,27 @@
     }
 
     /**
+     * Get the most recent identified battery level of this Bluetooth device
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @return Battery level in percents from 0 to 100, or {@link #BATTERY_LEVEL_UNKNOWN} if
+     *         Bluetooth is disabled, or device is disconnected, or does not have any battery
+     *         reporting service, or return value is invalid
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getBatteryLevel() {
+        if (sService == null) {
+            Log.e(TAG, "Bluetooth disabled. Cannot get remote device battery level");
+            return BATTERY_LEVEL_UNKNOWN;
+        }
+        try {
+            return sService.getBatteryLevel(this);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return BATTERY_LEVEL_UNKNOWN;
+    }
+
+    /**
      * Start the bonding (pairing) process with the remote device.
      * <p>This is an asynchronous call, it will return immediately. Register
      * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when
@@ -1720,6 +1769,38 @@
     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
                                      BluetoothGattCallback callback, int transport, int phy,
                                      Handler handler) {
+        return connectGatt(context, autoConnect, callback, transport, false, phy, handler);
+    }
+
+    /**
+     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
+     * The callback is used to deliver results to Caller, such as connection status as well
+     * as any further GATT client operations.
+     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
+     * GATT client operations.
+     * @param callback GATT callback handler that will receive asynchronous callbacks.
+     * @param autoConnect Whether to directly connect to the remote device (false)
+     *                    or to automatically connect as soon as the remote
+     *                    device becomes available (true).
+     * @param transport preferred transport for GATT connections to remote dual-mode devices
+     *             {@link BluetoothDevice#TRANSPORT_AUTO} or
+     *             {@link BluetoothDevice#TRANSPORT_BREDR} or {@link BluetoothDevice#TRANSPORT_LE}
+     * @param opportunistic Whether this GATT client is opportunistic. An opportunistic GATT client
+     *                      does not hold a GATT connection. It automatically disconnects when no
+     *                      other GATT connections are active for the remote device.
+     * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of
+     *             {@link BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK},
+     *             an d{@link BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect
+     *             if {@code autoConnect} is set to true.
+     * @param handler The handler to use for the callback. If {@code null}, callbacks will happen
+     *             on an un-specified background thread.
+     * @return A BluetoothGatt instance. You can use BluetoothGatt to conduct GATT client
+     *         operations.
+     * @hide
+     */
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+                                     BluetoothGattCallback callback, int transport,
+                                     boolean opportunistic, int phy, Handler handler) {
         if (callback == null)
             throw new NullPointerException("callback is null");
 
@@ -1733,7 +1814,7 @@
                 // BLE is not supported
                 return null;
             }
-            BluetoothGatt gatt = new BluetoothGatt(iGatt, this, transport, phy);
+            BluetoothGatt gatt = new BluetoothGatt(iGatt, this, transport, opportunistic, phy);
             gatt.connect(autoConnect, callback, handler);
             return gatt;
         } catch (RemoteException e) {Log.e(TAG, "", e);}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index b12ff72..1307f07 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -53,6 +53,7 @@
     private Boolean mDeviceBusy = false;
     private int mTransport;
     private int mPhy;
+    private boolean mOpportunistic;
 
     private static final int AUTH_RETRY_STATE_IDLE = 0;
     private static final int AUTH_RETRY_STATE_NO_MITM = 1;
@@ -172,7 +173,7 @@
                 }
                 try {
                     mService.clientConnect(mClientIf, mDevice.getAddress(),
-                                           !mAutoConnect, mTransport, mPhy); // autoConnect is inverse of "isDirect"
+                                           !mAutoConnect, mTransport, mOpportunistic, mPhy); // autoConnect is inverse of "isDirect"
                 } catch (RemoteException e) {
                     Log.e(TAG,"",e);
                 }
@@ -628,11 +629,12 @@
         };
 
     /*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
-                                int transport, int phy) {
+                                int transport, boolean opportunistic, int phy) {
         mService = iGatt;
         mDevice = device;
         mTransport = transport;
         mPhy = phy;
+        mOpportunistic = opportunistic;
         mServices = new ArrayList<BluetoothGattService>();
 
         mConnState = CONN_STATE_IDLE;
@@ -839,8 +841,8 @@
      */
     public boolean connect() {
         try {
-            mService.clientConnect(mClientIf, mDevice.getAddress(),
-                                   false, mTransport, mPhy); // autoConnect is inverse of "isDirect"
+            mService.clientConnect(mClientIf, mDevice.getAddress(), false, mTransport,
+                    mOpportunistic, mPhy); // autoConnect is inverse of "isDirect"
             return true;
         } catch (RemoteException e) {
             Log.e(TAG,"",e);
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index c31a9b2..336f330 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -199,6 +199,37 @@
     public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
 
     /**
+     * A vendor-specific AT command
+     * @hide
+     */
+    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
+
+    /**
+     * A vendor-specific AT command
+     * @hide
+     */
+    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
+
+    /**
+     * Battery level indicator associated with
+     * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
+     * @hide
+     */
+    public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
+
+    /**
+     * A vendor-specific AT command
+     * @hide
+     */
+    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
+
+    /**
+     * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
+     * @hide
+     */
+    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
+
+    /**
      * Headset state when SCO audio is not connected.
      * This state can be one of
      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
@@ -226,34 +257,33 @@
      *
      * <p>This intent will have 3 extras:
      * <ul>
-     *   <li> {@link #EXTRA_IND_ID} - The Assigned number of headset Indicator which is supported by
-                                        the headset ( as indicated by AT+BIND
-                                        command in the SLC sequence).or whose value
-                                        is changed (indicated by AT+BIEV command)</li>
-     *   <li> {@link #EXTRA_IND_VALUE}- The updated value of headset indicator. </li>
-     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     *   <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
+     *              is supported by the headset ( as indicated by AT+BIND command in the SLC
+     *              sequence) or whose value is changed (indicated by AT+BIEV command) </li>
+     *   <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
+     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
      * </ul>
-     * <p>{@link #EXTRA_IND_ID} is defined by Bluetooth SIG and each of the indicators are
-     * given an assigned number. Below shows the assigned number of Indicator added so far
-     * - Enhanced Safety - 1
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
+     * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
+     *     are given an assigned number. Below shows the assigned number of Indicator added so far
+     * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
+     * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
      * @hide
      */
     public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
             "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
 
     /**
-     * A String extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
-     * intents that contains the UUID of the headset  indicator (as defined by Bluetooth SIG)
-     * that is being sent.
+     * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
+     * intents that contains the assigned number of the headset indicator as defined by
+     * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
      * @hide
      */
     public static final String EXTRA_HF_INDICATORS_IND_ID =
             "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
 
     /**
-     * A int  extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
+     * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
      * intents that contains the value of the Headset indicator that is being sent.
      * @hide
      */
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 43c5ae4..1d7cfc9 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -75,6 +75,7 @@
     ParcelUuid[] getRemoteUuids(in BluetoothDevice device);
     boolean fetchRemoteUuids(in BluetoothDevice device);
     boolean sdpSearch(in BluetoothDevice device, in ParcelUuid uuid);
+    int getBatteryLevel(in BluetoothDevice device);
 
     boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode);
     boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[]
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 4ff5976..05cef60 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -70,7 +70,7 @@
     void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
 
     void unregisterClient(in int clientIf);
-    void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport, in int phy);
+    void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport, in boolean opportunistic, in int phy);
     void clientDisconnect(in int clientIf, in String address);
     void clientSetPreferredPhy(in int clientIf, in String address, in int txPhy, in int rxPhy, in int phyOptions);
     void clientReadPhy(in int clientIf, in String address);
diff --git a/core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl b/core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl
index feccdce..0da4e88 100644
--- a/core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl
@@ -21,7 +21,7 @@
  *
  * {@hide}
  */
-interface IBluetoothStateChangeCallback
+oneway interface IBluetoothStateChangeCallback
 {
     void onBluetoothStateChange(boolean on);
 }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 02f0f18..55127a8 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -579,6 +579,8 @@
     /** {@hide} */
     public static final int MAX_NETWORK_TYPE = TYPE_VPN;
 
+    private static final int MIN_NETWORK_TYPE = TYPE_MOBILE;
+
     /**
      * If you want to set the default network preference,you can directly
      * change the networkAttributes array in framework's config.xml.
@@ -636,7 +638,7 @@
      *             validate a network type.
      */
     public static boolean isNetworkTypeValid(int networkType) {
-        return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
+        return MIN_NETWORK_TYPE <= networkType && networkType <= MAX_NETWORK_TYPE;
     }
 
     /**
@@ -649,6 +651,8 @@
      */
     public static String getNetworkTypeName(int type) {
         switch (type) {
+          case TYPE_NONE:
+                return "NONE";
             case TYPE_MOBILE:
                 return "MOBILE";
             case TYPE_WIFI:
diff --git a/core/java/android/net/ITetheringStatsProvider.aidl b/core/java/android/net/ITetheringStatsProvider.aidl
new file mode 100644
index 0000000..769086d
--- /dev/null
+++ b/core/java/android/net/ITetheringStatsProvider.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import android.net.NetworkStats;
+
+/**
+ * Interface that allows NetworkManagementService to query for tethering statistics.
+ *
+ * TODO: this does not really need to be an interface since Tethering runs in the same process
+ * as NetworkManagementService. Consider refactoring Tethering to use direct access to
+ * NetworkManagementService instead of using INetworkManagementService, and then deleting this
+ * interface.
+ *
+ * @hide
+ */
+interface ITetheringStatsProvider {
+    NetworkStats getTetherStats();
+}
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 48b095d..5ae3400 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -16,8 +16,10 @@
 package android.net;
 
 import android.annotation.StringDef;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import com.android.internal.util.HexDump;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -182,4 +184,17 @@
                 return false;
         }
     }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("{mName=")
+                .append(mName)
+                .append(", mKey=")
+                .append(Build.IS_DEBUGGABLE ? HexDump.toHexString(mKey) : "<hidden>")
+                .append(", mTruncLenBits=")
+                .append(mTruncLenBits)
+                .append("}")
+                .toString();
+    }
 };
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 8b80f2b..5a5c740 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -47,9 +47,22 @@
 
         // Authentication Algorithm
         IpSecAlgorithm authentication;
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{spiResourceId=")
+                    .append(spiResourceId)
+                    .append(", encryption=")
+                    .append(encryption)
+                    .append(", authentication=")
+                    .append(authentication)
+                    .append("}")
+                    .toString();
+        }
     }
 
-    Flow[] flow = new Flow[] {new Flow(), new Flow()};
+    final Flow[] flow = new Flow[] {new Flow(), new Flow()};
 
     // For tunnel mode IPv4 UDP Encapsulation
     // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
@@ -166,6 +179,35 @@
         encapRemotePort = in.readInt();
     }
 
+    @Override
+    public String toString() {
+        StringBuilder strBuilder = new StringBuilder();
+        strBuilder
+                .append("{mode=")
+                .append(mode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
+                .append(", localAddress=")
+                .append(localAddress)
+                .append(", remoteAddress=")
+                .append(remoteAddress)
+                .append(", network=")
+                .append(network)
+                .append(", encapType=")
+                .append(encapType)
+                .append(", encapLocalPortResourceId=")
+                .append(encapLocalPortResourceId)
+                .append(", encapRemotePort=")
+                .append(encapRemotePort)
+                .append(", nattKeepaliveInterval=")
+                .append(nattKeepaliveInterval)
+                .append(", flow[OUT]=")
+                .append(flow[IpSecTransform.DIRECTION_OUT])
+                .append(", flow[IN]=")
+                .append(flow[IpSecTransform.DIRECTION_IN])
+                .append("}");
+
+        return strBuilder.toString();
+    }
+
     public static final Parcelable.Creator<IpSecConfig> CREATOR =
             new Parcelable.Creator<IpSecConfig>() {
                 public IpSecConfig createFromParcel(Parcel in) {
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index e65f534..cfbac58b 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -26,6 +26,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import dalvik.system.CloseGuard;
 import java.io.IOException;
@@ -66,10 +67,10 @@
     public @interface TransformDirection {}
 
     /** @hide */
-    private static final int MODE_TUNNEL = 0;
+    public static final int MODE_TUNNEL = 0;
 
     /** @hide */
-    private static final int MODE_TRANSPORT = 1;
+    public static final int MODE_TRANSPORT = 1;
 
     /** @hide */
     public static final int ENCAP_NONE = 0;
@@ -487,5 +488,14 @@
             mContext = context;
             mConfig = new IpSecConfig();
         }
+
+        /**
+         * Return an {@link IpSecConfig} object for testing purposes.
+         * @hide
+         */
+        @VisibleForTesting
+        public IpSecConfig getIpSecConfig() {
+            return mConfig;
+        }
     }
 }
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index 6e74f14..62de991 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -16,6 +16,15 @@
 
 package android.net;
 
+import static android.system.OsConstants.IFA_F_DADFAILED;
+import static android.system.OsConstants.IFA_F_DEPRECATED;
+import static android.system.OsConstants.IFA_F_OPTIMISTIC;
+import static android.system.OsConstants.IFA_F_TENTATIVE;
+import static android.system.OsConstants.RT_SCOPE_HOST;
+import static android.system.OsConstants.RT_SCOPE_LINK;
+import static android.system.OsConstants.RT_SCOPE_SITE;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
+
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Pair;
@@ -26,15 +35,6 @@
 import java.net.InterfaceAddress;
 import java.net.UnknownHostException;
 
-import static android.system.OsConstants.IFA_F_DADFAILED;
-import static android.system.OsConstants.IFA_F_DEPRECATED;
-import static android.system.OsConstants.IFA_F_OPTIMISTIC;
-import static android.system.OsConstants.IFA_F_TENTATIVE;
-import static android.system.OsConstants.RT_SCOPE_HOST;
-import static android.system.OsConstants.RT_SCOPE_LINK;
-import static android.system.OsConstants.RT_SCOPE_SITE;
-import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
-
 /**
  * Identifies an IP address on a network link.
  *
@@ -101,7 +101,7 @@
      * Per RFC 4193 section 8, fc00::/7 identifies these addresses.
      */
     private boolean isIPv6ULA() {
-        if (address != null && address instanceof Inet6Address) {
+        if (address instanceof Inet6Address) {
             byte[] bytes = address.getAddress();
             return ((bytes[0] & (byte)0xfe) == (byte)0xfc);
         }
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 2dd7f75..d2af023 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -21,8 +21,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.BitUtils;
+import com.android.internal.util.Preconditions;
 
 import java.util.Objects;
+import java.util.StringJoiner;
 
 /**
  * This class represents the capabilities of a network.  This is used both to specify
@@ -346,11 +348,6 @@
         return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
     }
 
-    private boolean equalsNetCapabilitiesImmutable(NetworkCapabilities that) {
-        return ((this.mNetworkCapabilities & ~MUTABLE_CAPABILITIES) ==
-                (that.mNetworkCapabilities & ~MUTABLE_CAPABILITIES));
-    }
-
     private boolean equalsNetCapabilitiesRequestable(NetworkCapabilities that) {
         return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
                 (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES));
@@ -420,7 +417,6 @@
 
     /**
      * Indicates this network uses a LoWPAN transport.
-     * @hide
      */
     public static final int TRANSPORT_LOWPAN = 6;
 
@@ -429,6 +425,11 @@
     /** @hide */
     public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
 
+    /** @hide */
+    public static boolean isValidTransport(int transportType) {
+        return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT);
+    }
+
     private static final String[] TRANSPORT_NAMES = {
         "CELLULAR",
         "WIFI",
@@ -453,9 +454,7 @@
      * @hide
      */
     public NetworkCapabilities addTransportType(int transportType) {
-        if (transportType < MIN_TRANSPORT || transportType > MAX_TRANSPORT) {
-            throw new IllegalArgumentException("TransportType out of range");
-        }
+        checkValidTransportType(transportType);
         mTransportTypes |= 1 << transportType;
         setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
         return this;
@@ -469,9 +468,7 @@
      * @hide
      */
     public NetworkCapabilities removeTransportType(int transportType) {
-        if (transportType < MIN_TRANSPORT || transportType > MAX_TRANSPORT) {
-            throw new IllegalArgumentException("TransportType out of range");
-        }
+        checkValidTransportType(transportType);
         mTransportTypes &= ~(1 << transportType);
         setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
         return this;
@@ -495,19 +492,18 @@
      * @return {@code true} if set on this instance.
      */
     public boolean hasTransport(int transportType) {
-        if (transportType < MIN_TRANSPORT || transportType > MAX_TRANSPORT) {
-            return false;
-        }
-        return ((mTransportTypes & (1 << transportType)) != 0);
+        return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0);
     }
 
     private void combineTransportTypes(NetworkCapabilities nc) {
         this.mTransportTypes |= nc.mTransportTypes;
     }
+
     private boolean satisfiedByTransportTypes(NetworkCapabilities nc) {
         return ((this.mTransportTypes == 0) ||
                 ((this.mTransportTypes & nc.mTransportTypes) != 0));
     }
+
     /** @hide */
     public boolean equalsTransportTypes(NetworkCapabilities nc) {
         return (nc.mTransportTypes == this.mTransportTypes);
@@ -762,15 +758,43 @@
 
     /**
      * Checks that our immutable capabilities are the same as those of the given
-     * {@code NetworkCapabilities}.
+     * {@code NetworkCapabilities} and return a String describing any difference.
+     * The returned String is empty if there is no difference.
      *
      * @hide
      */
-    public boolean equalImmutableCapabilities(NetworkCapabilities nc) {
-        if (nc == null) return false;
-        return (equalsNetCapabilitiesImmutable(nc) &&
-                equalsTransportTypes(nc) &&
-                equalsSpecifier(nc));
+    public String describeImmutableDifferences(NetworkCapabilities that) {
+        if (that == null) {
+            return "other NetworkCapabilities was null";
+        }
+
+        StringJoiner joiner = new StringJoiner(", ");
+
+        // TODO: consider only enforcing that capabilities are not removed, allowing addition.
+        // Ignore NOT_METERED being added or removed as it is effectively dynamic. http://b/63326103
+        // TODO: properly support NOT_METERED as a mutable and requestable capability.
+        final long mask = ~MUTABLE_CAPABILITIES & ~NET_CAPABILITY_NOT_METERED;
+        long oldImmutableCapabilities = this.mNetworkCapabilities & mask;
+        long newImmutableCapabilities = that.mNetworkCapabilities & mask;
+        if (oldImmutableCapabilities != newImmutableCapabilities) {
+            String before = capabilityNamesOf(BitUtils.unpackBits(oldImmutableCapabilities));
+            String after = capabilityNamesOf(BitUtils.unpackBits(newImmutableCapabilities));
+            joiner.add(String.format("immutable capabilities changed: %s -> %s", before, after));
+        }
+
+        if (!equalsSpecifier(that)) {
+            NetworkSpecifier before = this.getNetworkSpecifier();
+            NetworkSpecifier after = that.getNetworkSpecifier();
+            joiner.add(String.format("specifier changed: %s -> %s", before, after));
+        }
+
+        if (!equalsTransportTypes(that)) {
+            String before = transportNamesOf(this.getTransportTypes());
+            String after = transportNamesOf(that.getTransportTypes());
+            joiner.add(String.format("transports changed: %s -> %s", before, after));
+        }
+
+        return joiner.toString();
     }
 
     /**
@@ -845,33 +869,15 @@
 
     @Override
     public String toString() {
+        // TODO: enumerate bits for transports and capabilities instead of creating arrays.
+        // TODO: use a StringBuilder instead of string concatenation.
         int[] types = getTransportTypes();
         String transports = (types.length > 0) ? " Transports: " + transportNamesOf(types) : "";
 
         types = getCapabilities();
         String capabilities = (types.length > 0 ? " Capabilities: " : "");
         for (int i = 0; i < types.length; ) {
-            switch (types[i]) {
-                case NET_CAPABILITY_MMS:            capabilities += "MMS"; break;
-                case NET_CAPABILITY_SUPL:           capabilities += "SUPL"; break;
-                case NET_CAPABILITY_DUN:            capabilities += "DUN"; break;
-                case NET_CAPABILITY_FOTA:           capabilities += "FOTA"; break;
-                case NET_CAPABILITY_IMS:            capabilities += "IMS"; break;
-                case NET_CAPABILITY_CBS:            capabilities += "CBS"; break;
-                case NET_CAPABILITY_WIFI_P2P:       capabilities += "WIFI_P2P"; break;
-                case NET_CAPABILITY_IA:             capabilities += "IA"; break;
-                case NET_CAPABILITY_RCS:            capabilities += "RCS"; break;
-                case NET_CAPABILITY_XCAP:           capabilities += "XCAP"; break;
-                case NET_CAPABILITY_EIMS:           capabilities += "EIMS"; break;
-                case NET_CAPABILITY_NOT_METERED:    capabilities += "NOT_METERED"; break;
-                case NET_CAPABILITY_INTERNET:       capabilities += "INTERNET"; break;
-                case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break;
-                case NET_CAPABILITY_TRUSTED:        capabilities += "TRUSTED"; break;
-                case NET_CAPABILITY_NOT_VPN:        capabilities += "NOT_VPN"; break;
-                case NET_CAPABILITY_VALIDATED:      capabilities += "VALIDATED"; break;
-                case NET_CAPABILITY_CAPTIVE_PORTAL: capabilities += "CAPTIVE_PORTAL"; break;
-                case NET_CAPABILITY_FOREGROUND:     capabilities += "FOREGROUND"; break;
-            }
+            capabilities += capabilityNameOf(types[i]);
             if (++i < types.length) capabilities += "&";
         }
 
@@ -891,24 +897,69 @@
     /**
      * @hide
      */
+    public static String capabilityNamesOf(int[] capabilities) {
+        StringJoiner joiner = new StringJoiner("|");
+        if (capabilities != null) {
+            for (int c : capabilities) {
+                joiner.add(capabilityNameOf(c));
+            }
+        }
+        return joiner.toString();
+    }
+
+    /**
+     * @hide
+     */
+    public static String capabilityNameOf(int capability) {
+        switch (capability) {
+            case NET_CAPABILITY_MMS:            return "MMS";
+            case NET_CAPABILITY_SUPL:           return "SUPL";
+            case NET_CAPABILITY_DUN:            return "DUN";
+            case NET_CAPABILITY_FOTA:           return "FOTA";
+            case NET_CAPABILITY_IMS:            return "IMS";
+            case NET_CAPABILITY_CBS:            return "CBS";
+            case NET_CAPABILITY_WIFI_P2P:       return "WIFI_P2P";
+            case NET_CAPABILITY_IA:             return "IA";
+            case NET_CAPABILITY_RCS:            return "RCS";
+            case NET_CAPABILITY_XCAP:           return "XCAP";
+            case NET_CAPABILITY_EIMS:           return "EIMS";
+            case NET_CAPABILITY_NOT_METERED:    return "NOT_METERED";
+            case NET_CAPABILITY_INTERNET:       return "INTERNET";
+            case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED";
+            case NET_CAPABILITY_TRUSTED:        return "TRUSTED";
+            case NET_CAPABILITY_NOT_VPN:        return "NOT_VPN";
+            case NET_CAPABILITY_VALIDATED:      return "VALIDATED";
+            case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
+            case NET_CAPABILITY_FOREGROUND:     return "FOREGROUND";
+            default:                            return Integer.toString(capability);
+        }
+    }
+
+    /**
+     * @hide
+     */
     public static String transportNamesOf(int[] types) {
-        if (types == null || types.length == 0) {
-            return "";
+        StringJoiner joiner = new StringJoiner("|");
+        if (types != null) {
+            for (int t : types) {
+                joiner.add(transportNameOf(t));
+            }
         }
-        StringBuilder transports = new StringBuilder();
-        for (int t : types) {
-            transports.append("|").append(transportNameOf(t));
-        }
-        return transports.substring(1);
+        return joiner.toString();
     }
 
     /**
      * @hide
      */
     public static String transportNameOf(int transport) {
-        if (transport < 0 || TRANSPORT_NAMES.length <= transport) {
+        if (!isValidTransport(transport)) {
             return "UNKNOWN";
         }
         return TRANSPORT_NAMES[transport];
     }
+
+    private static void checkValidTransportType(int transport) {
+        Preconditions.checkArgument(
+                isValidTransport(transport), "Invalid TransportType " + transport);
+    }
 }
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 42f5feb..84c32be 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -127,7 +127,8 @@
      * @hide
      */
     public NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
-        if (!ConnectivityManager.isNetworkTypeValid(type)) {
+        if (!ConnectivityManager.isNetworkTypeValid(type)
+                && type != ConnectivityManager.TYPE_NONE) {
             throw new IllegalArgumentException("Invalid network type: " + type);
         }
         mNetworkType = type;
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
new file mode 100644
index 0000000..0f1e259
--- /dev/null
+++ b/core/java/android/net/OWNERS
@@ -0,0 +1,6 @@
+ek@google.com
+hugobenichi@google.com
+jsharkey@google.com
+lorenzo@google.com
+satk@google.com
+silberst@google.com
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index e0a026e..007846e 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -42,10 +42,13 @@
     /** @hide */ public static final int ERROR_STARTING_IPV6 = 5;
     /** @hide */ public static final int ERROR_STARTING_IPREACHABILITYMONITOR = 6;
 
+    /** @hide */ public static final int ERROR_INVALID_PROVISIONING = 7;
+
     /** {@hide} */
     @IntDef(value = {
             PROVISIONING_OK, PROVISIONING_FAIL, COMPLETE_LIFECYCLE,
             ERROR_STARTING_IPV4, ERROR_STARTING_IPV6, ERROR_STARTING_IPREACHABILITYMONITOR,
+            ERROR_INVALID_PROVISIONING,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index f991efe..6801618 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -29,6 +29,7 @@
 import android.nfc.INfcFCardEmulation;
 import android.nfc.INfcUnlockHandler;
 import android.nfc.ITagRemovedCallback;
+import android.nfc.INfcDta;
 import android.os.Bundle;
 
 /**
@@ -40,7 +41,7 @@
     INfcCardEmulation getNfcCardEmulationInterface();
     INfcFCardEmulation getNfcFCardEmulationInterface();
     INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg);
-
+    INfcDta getNfcDtaInterface(in String pkg);
     int getState();
     boolean disable(boolean saveState);
     boolean enable();
diff --git a/core/java/android/nfc/INfcDta.aidl b/core/java/android/nfc/INfcDta.aidl
new file mode 100644
index 0000000..4cc5927
--- /dev/null
+++ b/core/java/android/nfc/INfcDta.aidl
@@ -0,0 +1,34 @@
+ /*
+  * Copyright (C) 2017 NXP Semiconductors
+  *
+  * 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.nfc;
+
+import android.os.Bundle;
+
+/**
+ * {@hide}
+ */
+interface INfcDta {
+
+    void enableDta();
+    void disableDta();
+    boolean enableServer(String serviceName, int serviceSap, int miu,
+            int rwSize,int testCaseId);
+    void disableServer();
+    boolean enableClient(String serviceName, int miu, int rwSize,
+            int testCaseId);
+    void disableClient();
+    boolean registerMessageService(String msgServiceName);
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 783c25a..fab494a 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -16,8 +16,6 @@
 
 package android.nfc;
 
-import java.util.HashMap;
-
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -42,6 +40,7 @@
 import android.util.Log;
 
 import java.io.IOException;
+import java.util.HashMap;
 
 /**
  * Represents the local NFC adapter.
@@ -626,6 +625,23 @@
     }
 
     /**
+     * Returns the binder interface to the NFC-DTA test interface.
+     * @hide
+     */
+    public INfcDta getNfcDtaInterface() {
+        if (mContext == null) {
+            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+                    + " NFC extras APIs");
+        }
+        try {
+            return sService.getNfcDtaInterface(mContext.getPackageName());
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return null;
+        }
+    }
+
+    /**
      * NFC service dead - attempt best effort recovery
      * @hide
      */
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 7678678..218e4f2 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -253,6 +253,20 @@
                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
                     }
                     a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG &&
+                        tagName.equals("aid-suffix-filter") && currentGroup != null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.AidFilter);
+                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
+                            toUpperCase();
+                    // Add wildcard char to indicate suffix
+                    aid = aid.concat("#");
+                    if (CardEmulation.isValidAid(aid) && !currentGroup.aids.contains(aid)) {
+                        currentGroup.aids.add(aid);
+                    } else {
+                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+                    }
+                    a.recycle();
                 }
             }
         } catch (NameNotFoundException e) {
@@ -297,6 +311,17 @@
         return prefixAids;
     }
 
+    public List<String> getSubsetAids() {
+        final ArrayList<String> subsetAids = new ArrayList<String>();
+        for (AidGroup group : getAidGroups()) {
+            for (String aid : group.aids) {
+                if (aid.endsWith("#")) {
+                    subsetAids.add(aid);
+                }
+            }
+        }
+        return subsetAids;
+    }
     /**
      * Returns the registered AID group for this category.
      */
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index b49288e..6dd7993 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -606,6 +606,8 @@
      * <li>Consist of only hex characters
      * <li>Additionally, we allow an asterisk at the end, to indicate
      *     a prefix
+     * <li>Additinally we allow an (#) at symbol at the end, to indicate
+     *     a subset
      * </ul>
      *
      * @hide
@@ -614,20 +616,20 @@
         if (aid == null)
             return false;
 
-        // If a prefix AID, the total length must be odd (even # of AID chars + '*')
-        if (aid.endsWith("*") && ((aid.length() % 2) == 0)) {
+        // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+        if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
             Log.e(TAG, "AID " + aid + " is not a valid AID.");
             return false;
         }
 
-        // If not a prefix AID, the total length must be even (even # of AID chars)
-        if (!aid.endsWith("*") && ((aid.length() % 2) != 0)) {
+        // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+        if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
             Log.e(TAG, "AID " + aid + " is not a valid AID.");
             return false;
         }
 
         // Verify hex characters
-        if (!aid.matches("[0-9A-Fa-f]{10,32}\\*?")) {
+        if (!aid.matches("[0-9A-Fa-f]{10,32}\\*?\\#?")) {
             Log.e(TAG, "AID " + aid + " is not a valid AID.");
             return false;
         }
diff --git a/core/java/android/nfc/cardemulation/HostNfcFService.java b/core/java/android/nfc/cardemulation/HostNfcFService.java
index 27c4976..fd0d8ad 100644
--- a/core/java/android/nfc/cardemulation/HostNfcFService.java
+++ b/core/java/android/nfc/cardemulation/HostNfcFService.java
@@ -64,6 +64,7 @@
  *           android:description="@string/servicedesc"&gt;
  *       &lt;system-code-filter android:name="4000"/&gt;
  *       &lt;nfcid2-filter android:name="02FE000000000000"/&gt;
+         &lt;t3tPmm-filter android:name="FFFFFFFFFFFFFFFF"/&gt;
  * &lt;/host-nfcf-service&gt;
  * </pre>
  *
@@ -76,6 +77,7 @@
  * <ul>
  * <li>Exactly one {@link android.R.styleable#SystemCodeFilter &lt;system-code-filter&gt;} tag.</li>
  * <li>Exactly one {@link android.R.styleable#Nfcid2Filter &lt;nfcid2-filter&gt;} tag.</li>
+ * <li>Zero or one {@link android.R.styleable#T3tPmmFilter &lt;t3tPmm-filter&gt;} tag.</li>
  * </ul>
  * </p>
  *
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
index b93eec1..1d3f9c2 100644
--- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -18,9 +18,9 @@
 
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -44,6 +44,8 @@
 public final class NfcFServiceInfo implements Parcelable {
     static final String TAG = "NfcFServiceInfo";
 
+    private static final String DEFAULT_T3T_PMM = "FFFFFFFFFFFFFFFF";
+
     /**
      * The service that implements this
      */
@@ -80,11 +82,16 @@
     final int mUid;
 
     /**
+     * LF_T3T_PMM of the service
+     */
+    final String mT3tPmm;
+
+    /**
      * @hide
      */
     public NfcFServiceInfo(ResolveInfo info, String description,
             String systemCode, String dynamicSystemCode, String nfcid2, String dynamicNfcid2,
-            int uid) {
+            int uid, String t3tPmm) {
         this.mService = info;
         this.mDescription = description;
         this.mSystemCode = systemCode;
@@ -92,6 +99,7 @@
         this.mNfcid2 = nfcid2;
         this.mDynamicNfcid2 = dynamicNfcid2;
         this.mUid = uid;
+        this.mT3tPmm = t3tPmm;
     }
 
     public NfcFServiceInfo(PackageManager pm, ResolveInfo info)
@@ -130,6 +138,7 @@
 
             String systemCode = null;
             String nfcid2 = null;
+            String t3tPmm = null;
             final int depth = parser.getDepth();
 
             while (((eventType = parser.next()) != XmlPullParser.END_TAG ||
@@ -160,10 +169,18 @@
                         nfcid2 = null;
                     }
                     a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG && tagName.equals("t3tPmm-filter")
+                        && t3tPmm == null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.T3tPmmFilter);
+                    t3tPmm = a.getString(
+                            com.android.internal.R.styleable.T3tPmmFilter_name).toUpperCase();
+                    a.recycle();
                 }
             }
             mSystemCode = (systemCode == null ? "NULL" : systemCode);
             mNfcid2 = (nfcid2 == null ? "NULL" : nfcid2);
+            mT3tPmm = (t3tPmm == null ? DEFAULT_T3T_PMM : t3tPmm);
         } catch (NameNotFoundException e) {
             throw new XmlPullParserException("Unable to create context for: " + si.packageName);
         } finally {
@@ -202,6 +219,10 @@
         return mUid;
     }
 
+    public String getT3tPmm() {
+        return mT3tPmm;
+    }
+
     public CharSequence loadLabel(PackageManager pm) {
         return mService.loadLabel(pm);
     }
@@ -223,6 +244,7 @@
         if (mDynamicNfcid2 != null) {
             out.append(", dynamic NFCID2: " + mDynamicNfcid2);
         }
+        out.append(", T3T PMM:" + mT3tPmm);
         return out.toString();
     }
 
@@ -235,7 +257,7 @@
         if (!thatService.getComponent().equals(this.getComponent())) return false;
         if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false;
         if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false;
-
+        if (!thatService.mT3tPmm.equalsIgnoreCase(this.mT3tPmm)) return false;
         return true;
     }
 
@@ -264,6 +286,7 @@
             dest.writeString(mDynamicNfcid2);
         }
         dest.writeInt(mUid);
+        dest.writeString(mT3tPmm);
     };
 
     public static final Parcelable.Creator<NfcFServiceInfo> CREATOR =
@@ -283,8 +306,9 @@
                 dynamicNfcid2 = source.readString();
             }
             int uid = source.readInt();
+            String t3tPmm = source.readString();
             NfcFServiceInfo service = new NfcFServiceInfo(info, description,
-                    systemCode, dynamicSystemCode, nfcid2, dynamicNfcid2, uid);
+                    systemCode, dynamicSystemCode, nfcid2, dynamicNfcid2, uid, t3tPmm);
             return service;
         }
 
@@ -299,6 +323,7 @@
                 " (Description: " + getDescription() + ")");
         pw.println("    System Code: " + getSystemCode());
         pw.println("    NFCID2: " + getNfcid2());
+        pw.println("    T3tPmm: " + getT3tPmm());
     }
 }
 
diff --git a/core/java/android/nfc/dta/NfcDta.java b/core/java/android/nfc/dta/NfcDta.java
new file mode 100644
index 0000000..8801662
--- /dev/null
+++ b/core/java/android/nfc/dta/NfcDta.java
@@ -0,0 +1,167 @@
+/*
+ * 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.nfc.dta;
+
+import android.content.Context;
+import android.nfc.INfcDta;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * This class provides the primary API for DTA operations.
+ * @hide
+ */
+public final class NfcDta {
+    private static final String TAG = "NfcDta";
+
+    private static INfcDta sService;
+    private static HashMap<Context, NfcDta> sNfcDtas = new HashMap<Context, NfcDta>();
+
+    private final Context mContext;
+
+    private NfcDta(Context context, INfcDta service) {
+        mContext = context.getApplicationContext();
+        sService = service;
+    }
+
+    /**
+     * Helper to get an instance of this class.
+     *
+     * @param adapter A reference to an NfcAdapter object.
+     * @return
+     */
+    public static synchronized NfcDta getInstance(NfcAdapter adapter) {
+        if (adapter == null) throw new NullPointerException("NfcAdapter is null");
+        Context context = adapter.getContext();
+        if (context == null) {
+            Log.e(TAG, "NfcAdapter context is null.");
+            throw new UnsupportedOperationException();
+        }
+
+        NfcDta manager = sNfcDtas.get(context);
+        if (manager == null) {
+            INfcDta service = adapter.getNfcDtaInterface();
+            if (service == null) {
+                Log.e(TAG, "This device does not implement the INfcDta interface.");
+                throw new UnsupportedOperationException();
+            }
+            manager = new NfcDta(context, service);
+            sNfcDtas.put(context, manager);
+        }
+        return manager;
+    }
+
+    /**
+     * Enables DTA mode
+     *
+     * @return true/false if enabling was successful
+     */
+    public boolean enableDta() {
+        try {
+            sService.enableDta();
+        } catch (RemoteException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Disables DTA mode
+     *
+     * @return true/false if disabling was successful
+     */
+    public boolean disableDta() {
+        try {
+            sService.disableDta();
+        } catch (RemoteException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Enables Server
+     *
+     * @return true/false if enabling was successful
+     */
+    public boolean enableServer(String serviceName, int serviceSap, int miu,
+            int rwSize, int testCaseId) {
+        try {
+            return sService.enableServer(serviceName, serviceSap, miu, rwSize, testCaseId);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Disables Server
+     *
+     * @return true/false if disabling was successful
+     */
+    public boolean disableServer() {
+        try {
+            sService.disableServer();
+        } catch (RemoteException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Enables Client
+     *
+     * @return true/false if enabling was successful
+     */
+    public boolean enableClient(String serviceName, int miu, int rwSize,
+            int testCaseId) {
+        try {
+            return sService.enableClient(serviceName, miu, rwSize, testCaseId);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Disables client
+     *
+     * @return true/false if disabling was successful
+     */
+    public boolean disableClient() {
+        try {
+            sService.disableClient();
+        } catch (RemoteException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Registers Message Service
+     *
+     * @return true/false if registration was successful
+     */
+    public boolean registerMessageService(String msgServiceName) {
+        try {
+            return sService.registerMessageService(msgServiceName);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index e05bd89..55b6dc8 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -221,28 +221,69 @@
         /** @hide */
         public static final int OTHER_OTHER_MEMTRACK = 16;
 
+        // Needs to be declared here for the DVK_STAT ranges below.
+        /** @hide */
+        public static final int NUM_OTHER_STATS = 17;
+
+        // Dalvik subsections.
         /** @hide */
         public static final int OTHER_DALVIK_NORMAL = 17;
         /** @hide */
         public static final int OTHER_DALVIK_LARGE = 18;
         /** @hide */
-        public static final int OTHER_DALVIK_LINEARALLOC = 19;
+        public static final int OTHER_DALVIK_ZYGOTE = 19;
         /** @hide */
-        public static final int OTHER_DALVIK_ACCOUNTING = 20;
+        public static final int OTHER_DALVIK_NON_MOVING = 20;
+        // Section begins and ends for dumpsys, relative to the DALVIK categories.
         /** @hide */
-        public static final int OTHER_DALVIK_CODE_CACHE = 21;
+        public static final int OTHER_DVK_STAT_DALVIK_START =
+                OTHER_DALVIK_NORMAL - NUM_OTHER_STATS;
         /** @hide */
-        public static final int OTHER_DALVIK_ZYGOTE = 22;
+        public static final int OTHER_DVK_STAT_DALVIK_END =
+                OTHER_DALVIK_NON_MOVING - NUM_OTHER_STATS;
+
+        // Dalvik Other subsections.
         /** @hide */
-        public static final int OTHER_DALVIK_NON_MOVING = 23;
+        public static final int OTHER_DALVIK_OTHER_LINEARALLOC = 21;
         /** @hide */
-        public static final int OTHER_DALVIK_INDIRECT_REFERENCE_TABLE = 24;
+        public static final int OTHER_DALVIK_OTHER_ACCOUNTING = 22;
+        /** @hide */
+        public static final int OTHER_DALVIK_OTHER_CODE_CACHE = 23;
+        /** @hide */
+        public static final int OTHER_DALVIK_OTHER_COMPILER_METADATA = 24;
+        /** @hide */
+        public static final int OTHER_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE = 25;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DALVIK_OTHER_START =
+                OTHER_DALVIK_OTHER_LINEARALLOC - NUM_OTHER_STATS;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DALVIK_OTHER_END =
+                OTHER_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE - NUM_OTHER_STATS;
+
+        // Dex subsections (Boot vdex, App dex, and App vdex).
+        /** @hide */
+        public static final int OTHER_DEX_BOOT_VDEX = 26;
+        /** @hide */
+        public static final int OTHER_DEX_APP_DEX = 27;
+        /** @hide */
+        public static final int OTHER_DEX_APP_VDEX = 28;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DEX_START = OTHER_DEX_BOOT_VDEX - NUM_OTHER_STATS;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_DEX_END = OTHER_DEX_APP_VDEX - NUM_OTHER_STATS;
+
+        // Art subsections (App image, boot image).
+        /** @hide */
+        public static final int OTHER_ART_APP = 29;
+        /** @hide */
+        public static final int OTHER_ART_BOOT = 30;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_ART_START = OTHER_ART_APP - NUM_OTHER_STATS;
+        /** @hide */
+        public static final int OTHER_DVK_STAT_ART_END = OTHER_ART_BOOT - NUM_OTHER_STATS;
 
         /** @hide */
-        public static final int NUM_OTHER_STATS = 17;
-
-        /** @hide */
-        public static final int NUM_DVK_STATS = 8;
+        public static final int NUM_DVK_STATS = 14;
 
         /** @hide */
         public static final int NUM_CATEGORIES = 8;
@@ -406,12 +447,18 @@
                 case OTHER_OTHER_MEMTRACK: return "Other mtrack";
                 case OTHER_DALVIK_NORMAL: return ".Heap";
                 case OTHER_DALVIK_LARGE: return ".LOS";
-                case OTHER_DALVIK_LINEARALLOC: return ".LinearAlloc";
-                case OTHER_DALVIK_ACCOUNTING: return ".GC";
-                case OTHER_DALVIK_CODE_CACHE: return ".JITCache";
                 case OTHER_DALVIK_ZYGOTE: return ".Zygote";
                 case OTHER_DALVIK_NON_MOVING: return ".NonMoving";
-                case OTHER_DALVIK_INDIRECT_REFERENCE_TABLE: return ".IndirectRef";
+                case OTHER_DALVIK_OTHER_LINEARALLOC: return ".LinearAlloc";
+                case OTHER_DALVIK_OTHER_ACCOUNTING: return ".GC";
+                case OTHER_DALVIK_OTHER_CODE_CACHE: return ".JITCache";
+                case OTHER_DALVIK_OTHER_COMPILER_METADATA: return ".CompilerMetadata";
+                case OTHER_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE: return ".IndirectRef";
+                case OTHER_DEX_BOOT_VDEX: return ".Boot vdex";
+                case OTHER_DEX_APP_DEX: return ".App dex";
+                case OTHER_DEX_APP_VDEX: return ".App vdex";
+                case OTHER_ART_APP: return ".App art";
+                case OTHER_ART_BOOT: return ".Boot art";
                 default: return "????";
             }
         }
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index b09c51c..270e63f 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -51,6 +51,11 @@
             String serviceName)
         throws RemoteException, NoSuchElementException;
 
+    public static native final void configureRpcThreadpool(
+            long maxThreads, boolean callerWillJoin);
+
+    public static native final void joinRpcThreadpool();
+
     // Returns address of the "freeFunction".
     private static native final long native_init();
 
@@ -66,4 +71,13 @@
     }
 
     private long mNativeContext;
+
+    private static native void native_report_sysprop_change();
+
+    /**
+     * Notifies listeners that a system property has changed
+     */
+    public static void reportSyspropChanged() {
+        native_report_sysprop_change();
+    }
 }
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 92e78bc..3de2174 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -20,6 +20,7 @@
 import android.net.InterfaceConfiguration;
 import android.net.INetd;
 import android.net.INetworkManagementEventObserver;
+import android.net.ITetheringStatsProvider;
 import android.net.Network;
 import android.net.NetworkStats;
 import android.net.RouteInfo;
@@ -207,6 +208,18 @@
     void disableNat(String internalInterface, String externalInterface);
 
     /**
+     * Registers a {@code ITetheringStatsProvider} to provide tethering statistics.
+     * All registered providers will be called in order, and their results will be added together.
+     * Netd is always registered as a tethering stats provider.
+     */
+    void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name);
+
+    /**
+     * Unregisters a previously-registered {@code ITetheringStatsProvider}.
+     */
+    void unregisterTetheringStatsProvider(ITetheringStatsProvider provider);
+
+    /**
      ** PPPD
      **/
 
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index b5ab908..b84dfb3 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -130,7 +130,7 @@
  * caller of the read function must know what type to expect and pass in the
  * appropriate {@link Parcelable.Creator Parcelable.Creator} instead to
  * properly construct the new object and read its data.  (To more efficient
- * write and read a single Parceable object that is not null, you can directly
+ * write and read a single Parcelable object that is not null, you can directly
  * call {@link Parcelable#writeToParcel Parcelable.writeToParcel} and
  * {@link Parcelable.Creator#createFromParcel Parcelable.Creator.createFromParcel}
  * yourself.)</p>
diff --git a/core/java/android/provider/TimeZoneRulesDataContract.java b/core/java/android/provider/TimeZoneRulesDataContract.java
index 19e914b..7896385 100644
--- a/core/java/android/provider/TimeZoneRulesDataContract.java
+++ b/core/java/android/provider/TimeZoneRulesDataContract.java
@@ -24,7 +24,6 @@
  *
  * @hide
  */
-// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
 public final class TimeZoneRulesDataContract {
 
     private TimeZoneRulesDataContract() {}
@@ -40,75 +39,80 @@
     private static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
 
     /**
-     * The content:// style URI for determining what type of update is available.
-     *
-     * <p>The URI can be queried using
-     * {@link android.content.ContentProvider#query(Uri, String[], String, String[], String)};
-     * the result will be a cursor with a single row. If the {@link #COLUMN_OPERATION}
-     * column is {@link #OPERATION_INSTALL} then see {@link #DATA_URI} for how to obtain the
-     * binary data.
+     * Defines fields exposed through the {@link Operation#CONTENT_URI} for describing a time zone
+     * distro operation.
      */
-    public static final Uri OPERATION_URI = Uri.withAppendedPath(AUTHORITY_URI, "operation");
+    public static final class Operation {
 
-    /**
-     * The {@code String} column of the {@link #OPERATION_URI} that provides an int specifying the
-     * type of operation to perform. See {@link #OPERATION_NO_OP}, {@link #OPERATION_UNINSTALL} and
-     * {@link #OPERATION_INSTALL}.
-     */
-    public static final String COLUMN_OPERATION = "operation";
+        /** Not instantiable. */
+        private Operation() {
+        }
 
-    /**
-     * An operation type used when the time zone rules on device should be left as they are.
-     * This is not expected to be used in normal operation but a safe result in the event of an
-     * error that cannot be recovered from.
-     */
-    public static final String OPERATION_NO_OP = "NOOP";
+        /**
+         * The content:// style URI for determining what type of update is available.
+         *
+         * <p>The URI can be queried using
+         * {@link android.content.ContentProvider#query(Uri, String[], String, String[], String)};
+         * the result will be a cursor with a single row. If the {@link Operation#COLUMN_TYPE}
+         * column is {@link Operation#TYPE_INSTALL} then
+         * {@link android.content.ContentProvider#openFile(Uri, String)} can be used with "r" mode
+         * to obtain the binary data.
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "operation");
 
-    /**
-     * An operation type used when the current time zone rules on device should be uninstalled,
-     * returning to the values held in the system partition.
-     */
-    public static final String OPERATION_UNINSTALL = "UNINSTALL";
+        /**
+         * The {@code String} column of the {@link #CONTENT_URI} that provides an int specifying
+         * the type of operation to perform. See {@link #TYPE_NO_OP},
+         * {@link #TYPE_UNINSTALL} and {@link #TYPE_INSTALL}.
+         */
+        public static final String COLUMN_TYPE = "type";
 
-    /**
-     * An operation type used when the current time zone rules on device should be replaced by
-     * a new set obtained via the {@link android.content.ContentProvider#openFile(Uri, String)}
-     * method.
-     */
-    public static final String OPERATION_INSTALL = "INSTALL";
+        /**
+         * An operation type used when the current time zone rules on device should be replaced by
+         * a new set obtained via the {@link android.content.ContentProvider#openFile(Uri, String)}
+         * method.
+         */
+        public static final String TYPE_INSTALL = "INSTALL";
 
-    /**
-     * The {@code nullable int} column of the {@link #OPERATION_URI} that describes the major
-     * version of the distro to be installed.
-     * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
-     */
-    public static final String COLUMN_DISTRO_MAJOR_VERSION = "distro_major_version";
+        /**
+         * An operation type used when the current time zone rules on device should be uninstalled,
+         * returning to the values held in the system partition.
+         */
+        public static final String TYPE_UNINSTALL = "UNINSTALL";
 
-    /**
-     * The {@code nullable int} column of the {@link #OPERATION_URI} that describes the minor
-     * version of the distro to be installed.
-     * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
-     */
-    public static final String COLUMN_DISTRO_MINOR_VERSION = "distro_minor_version";
+        /**
+         * An operation type used when the time zone rules on device should be left as they are.
+         * This is not expected to be used in normal operation but a safe result in the event of an
+         * error that cannot be recovered from.
+         */
+        public static final String TYPE_NO_OP = "NOOP";
 
-    /**
-     * The {@code nullable String} column of the {@link #OPERATION_URI} that describes the IANA
-     * rules version of the distro to be installed.
-     * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
-     */
-    public static final String COLUMN_RULES_VERSION = "rules_version";
+        /**
+         * The {@code nullable int} column of the {@link #CONTENT_URI} that describes the major
+         * version of the distro to be installed.
+         * Only non-null if {@link #COLUMN_TYPE} contains {@link #TYPE_INSTALL}.
+         */
+        public static final String COLUMN_DISTRO_MAJOR_VERSION = "distro_major_version";
 
-    /**
-     * The {@code nullable int} column of the {@link #OPERATION_URI} that describes the revision
-     * number of the distro to be installed.
-     * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
-     */
-    public static final String COLUMN_REVISION = "revision";
+        /**
+         * The {@code nullable int} column of the {@link #CONTENT_URI} that describes the minor
+         * version of the distro to be installed.
+         * Only non-null if {@link #COLUMN_TYPE} contains {@link #TYPE_INSTALL}.
+         */
+        public static final String COLUMN_DISTRO_MINOR_VERSION = "distro_minor_version";
 
-    /**
-     * The content:// style URI for obtaining time zone bundle data.
-     *
-     * <p>Use {@link android.content.ContentProvider#openFile(Uri, String)} with "r" mode.
-     */
-    public static final Uri DATA_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
+        /**
+         * The {@code nullable String} column of the {@link #CONTENT_URI} that describes the IANA
+         * rules version of the distro to be installed.
+         * Only non-null if {@link #COLUMN_TYPE} contains {@link #TYPE_INSTALL}.
+         */
+        public static final String COLUMN_RULES_VERSION = "rules_version";
+
+        /**
+         * The {@code nullable int} column of the {@link #CONTENT_URI} that describes the revision
+         * number of the distro to be installed.
+         * Only non-null if {@link #COLUMN_TYPE} contains {@link #TYPE_INSTALL}.
+         */
+        public static final String COLUMN_REVISION = "revision";
+    }
 }
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
new file mode 100644
index 0000000..7120376
--- /dev/null
+++ b/core/java/android/security/OWNERS
@@ -0,0 +1,4 @@
+per-file NetworkSecurityPolicy.java = cbrubaker@google.com
+per-file NetworkSecurityPolicy.java = klyubin@google.com
+per-file FrameworkNetworkSecurityPolicy.java = cbrubaker@google.com
+per-file FrameworkNetworkSecurityPolicy.java = klyubin@google.com
diff --git a/core/java/android/security/net/config/OWNERS b/core/java/android/security/net/config/OWNERS
new file mode 100644
index 0000000..5350373
--- /dev/null
+++ b/core/java/android/security/net/config/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+cbrubaker@google.com
+klyubin@google.com
diff --git a/core/java/com/android/internal/app/procstats/PssTable.java b/core/java/com/android/internal/app/procstats/PssTable.java
index b6df983..de5f673 100644
--- a/core/java/com/android/internal/app/procstats/PssTable.java
+++ b/core/java/com/android/internal/app/procstats/PssTable.java
@@ -96,7 +96,7 @@
             }
 
             val = getValue(key, PSS_USS_AVERAGE);
-            setValue(key, PSS_AVERAGE,
+            setValue(key, PSS_USS_AVERAGE,
                     (long)(((val*(double)count)+(avgUss*(double)inCount)) / (count+inCount)));
 
             val = getValue(key, PSS_USS_MAXIMUM);
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 91429a6..f085d80 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -18,9 +18,13 @@
 
 import static android.system.OsConstants.F_SETFD;
 import static android.system.OsConstants.O_CLOEXEC;
+import static android.system.OsConstants.POLLIN;
 import static android.system.OsConstants.STDERR_FILENO;
 import static android.system.OsConstants.STDIN_FILENO;
 import static android.system.OsConstants.STDOUT_FILENO;
+import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEOUT_MILLIS;
+import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
+import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
 
 import android.net.Credentials;
 import android.net.LocalSocket;
@@ -31,13 +35,14 @@
 import android.os.Trace;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.system.StructPollfd;
 import android.util.Log;
 import dalvik.system.VMRuntime;
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -57,18 +62,6 @@
     private static final int[][] intArray2d = new int[0][0];
 
     /**
-     * {@link android.net.LocalSocket#setSoTimeout} value for connections.
-     * Effectively, the amount of time a requestor has between the start of
-     * the request and the completed request. The select-loop mode Zygote
-     * doesn't have the logic to return to the select loop in the middle of
-     * a request, so we need to time out here to avoid being denial-of-serviced.
-     */
-    private static final int CONNECTION_TIMEOUT_MILLIS = 1000;
-
-    /** max number of arguments that a connection can specify */
-    private static final int MAX_ZYGOTE_ARGC = 1024;
-
-    /**
      * The command socket.
      *
      * mSocket is retained in the child process in "peer wait" mode, so
@@ -807,17 +800,60 @@
 
         boolean usingWrapper = false;
         if (pipeFd != null && pid > 0) {
-            DataInputStream is = new DataInputStream(new FileInputStream(pipeFd));
             int innerPid = -1;
             try {
-                innerPid = is.readInt();
-            } catch (IOException ex) {
-                Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex);
-            } finally {
-                try {
-                    is.close();
-                } catch (IOException ex) {
+                // Do a busy loop here. We can't guarantee that a failure (and thus an exception
+                // bail) happens in a timely manner.
+                final int BYTES_REQUIRED = 4;  // Bytes in an int.
+
+                StructPollfd fds[] = new StructPollfd[] {
+                        new StructPollfd()
+                };
+
+                byte data[] = new byte[BYTES_REQUIRED];
+
+                int remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS;
+                int dataIndex = 0;
+                long startTime = System.nanoTime();
+
+                while (dataIndex < data.length && remainingSleepTime > 0) {
+                    fds[0].fd = pipeFd;
+                    fds[0].events = (short) POLLIN;
+                    fds[0].revents = 0;
+                    fds[0].userData = null;
+
+                    int res = android.system.Os.poll(fds, remainingSleepTime);
+                    long endTime = System.nanoTime();
+                    int elapsedTimeMs = (int)((endTime - startTime) / 1000000l);
+                    remainingSleepTime = WRAPPED_PID_TIMEOUT_MILLIS - elapsedTimeMs;
+
+                    if (res > 0) {
+                        if ((fds[0].revents & POLLIN) != 0) {
+                            // Only read one byte, so as not to block.
+                            int readBytes = android.system.Os.read(pipeFd, data, dataIndex, 1);
+                            if (readBytes < 0) {
+                                throw new RuntimeException("Some error");
+                            }
+                            dataIndex += readBytes;
+                        } else {
+                            // Error case. revents should contain one of the error bits.
+                            break;
+                        }
+                    } else if (res == 0) {
+                        Log.w(TAG, "Timed out waiting for child.");
+                    }
                 }
+
+                if (dataIndex == data.length) {
+                    DataInputStream is = new DataInputStream(new ByteArrayInputStream(data));
+                    innerPid = is.readInt();
+                }
+
+                if (innerPid == -1) {
+                    Log.w(TAG, "Error reading pid from wrapped process, child may have died");
+                }
+            } catch (Exception ex) {
+                Log.w(TAG, "Error reading pid from wrapped process, child may have died", ex);
             }
 
             // Ensure that the pid reported by the wrapped process is either the
diff --git a/core/java/com/android/internal/os/ZygoteConnectionConstants.java b/core/java/com/android/internal/os/ZygoteConnectionConstants.java
new file mode 100644
index 0000000..506e39f
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteConnectionConstants.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+/**
+ * Sharable zygote constants.
+ *
+ * @hide
+ */
+public class ZygoteConnectionConstants {
+    /**
+     * {@link android.net.LocalSocket#setSoTimeout} value for connections.
+     * Effectively, the amount of time a requestor has between the start of
+     * the request and the completed request. The select-loop mode Zygote
+     * doesn't have the logic to return to the select loop in the middle of
+     * a request, so we need to time out here to avoid being denial-of-serviced.
+     */
+    public static final int CONNECTION_TIMEOUT_MILLIS = 1000;
+
+    /** max number of arguments that a connection can specify */
+    public static final int MAX_ZYGOTE_ARGC = 1024;
+
+    /**
+     * Wait time for a wrapped app to report back its pid.
+     *
+     * We'll wait up to thirty seconds. This should give enough time for the fork
+     * to go through, but not to trigger the watchdog in the system server (by default
+     * sixty seconds).
+     *
+     * WARNING: This may trigger the watchdog in debug mode. However, to support
+     *          wrapping on lower-end devices we do not have much choice.
+     */
+    public static final int WRAPPED_PID_TIMEOUT_MILLIS = 30000;
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b7dff96..24e1b33 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -512,7 +512,7 @@
             try {
                 dexoptNeeded = DexFile.getDexOptNeeded(
                     classPathElement, instructionSet, "speed",
-                    false /* newProfile */);
+                    false /* newProfile */, false /* downgrade */);
             } catch (FileNotFoundException ignored) {
                 // Do not add to the classpath.
                 Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 5a50fbf..9151cee 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -195,6 +195,8 @@
                     "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG");
             addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter,
                     "/sys/fs/pstore/console-ramoops", -LOG_SIZE, "SYSTEM_LAST_KMSG");
+            addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter,
+                    "/sys/fs/pstore/console-ramoops-0", -LOG_SIZE, "SYSTEM_LAST_KMSG");
             addFileToDropBox(db, timestamps, headers, "/cache/recovery/log", -LOG_SIZE,
                     "SYSTEM_RECOVERY_LOG");
             addFileToDropBox(db, timestamps, headers, "/cache/recovery/last_kmsg",
@@ -276,6 +278,10 @@
         if (fileTime <= 0) {
             file = new File("/sys/fs/pstore/console-ramoops");
             fileTime = file.lastModified();
+            if (fileTime <= 0) {
+                file = new File("/sys/fs/pstore/console-ramoops-0");
+                fileTime = file.lastModified();
+            }
         }
 
         if (fileTime <= 0) return;  // File does not exist
diff --git a/core/java/com/android/server/net/OWNERS b/core/java/com/android/server/net/OWNERS
new file mode 100644
index 0000000..74f39a1
--- /dev/null
+++ b/core/java/com/android/server/net/OWNERS
@@ -0,0 +1,5 @@
+set noparent
+
+ek@google.com
+hugobenichi@google.com
+lorenzo@google.com
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2006be0..ca11404 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -233,6 +233,7 @@
         "libGLESv1_CM",
         "libGLESv2",
         "libvulkan",
+        "libziparchive",
         "libETC1",
         "libhardware",
         "libhardware_legacy",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 81504c0..9ef6052 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -33,8 +33,8 @@
 #include <SkImageDecoder.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
-#include "JniInvocation.h"
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/JniInvocation.h>
 #include "android_util_Binder.h"
 
 #include <stdio.h>
@@ -799,19 +799,27 @@
                             "--compiler-filter=", "-Ximage-compiler-option");
     }
 
-    // Make sure there is a preloaded-classes file.
-    if (!hasFile("/system/etc/preloaded-classes")) {
-        ALOGE("Missing preloaded-classes file, /system/etc/preloaded-classes not found: %s\n",
-              strerror(errno));
-        return -1;
-    }
-    addOption("-Ximage-compiler-option");
-    addOption("--image-classes=/system/etc/preloaded-classes");
-
-    // If there is a compiled-classes file, push it.
-    if (hasFile("/system/etc/compiled-classes")) {
+    // If there is a boot profile, it takes precedence over the image and preloaded classes.
+    if (hasFile("/system/etc/boot-image.prof")) {
         addOption("-Ximage-compiler-option");
-        addOption("--compiled-classes=/system/etc/compiled-classes");
+        addOption("--profile-file=/system/etc/boot-image.prof");
+        addOption("-Ximage-compiler-option");
+        addOption("--compiler-filter=speed-profile");
+    } else {
+        // Make sure there is a preloaded-classes file.
+        if (!hasFile("/system/etc/preloaded-classes")) {
+            ALOGE("Missing preloaded-classes file, /system/etc/preloaded-classes not found: %s\n",
+                  strerror(errno));
+            return -1;
+        }
+        addOption("-Ximage-compiler-option");
+        addOption("--image-classes=/system/etc/preloaded-classes");
+
+        // If there is a compiled-classes file, push it.
+        if (hasFile("/system/etc/compiled-classes")) {
+            addOption("-Ximage-compiler-option");
+            addOption("--compiled-classes=/system/etc/compiled-classes");
+        }
     }
 
     property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
@@ -1086,7 +1094,7 @@
      * Start VM.  This thread becomes the main thread of the VM, and will
      * not return until the VM exits.
      */
-    char* slashClassName = toSlashClassName(className);
+    char* slashClassName = toSlashClassName(className != NULL ? className : "");
     jclass startClass = env->FindClass(slashClassName);
     if (startClass == NULL) {
         ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 4001283..3866c84 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -15,7 +15,7 @@
 #include "Utils.h"
 #include "core_jni_helpers.h"
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <androidfw/Asset.h>
 #include <androidfw/ResourceTypes.h>
 #include <cutils/compiler.h>
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index a1ba42e..7d12f44 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -33,7 +33,7 @@
 #include "android_util_Binder.h"
 #include "core_jni_helpers.h"
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <androidfw/Asset.h>
 #include <binder/Parcel.h>
 #include <jni.h>
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
index 067489a..0e907c3 100644
--- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
@@ -5,7 +5,7 @@
 #include "SkTypes.h"
 #include "Utils.h"
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <memory>
 
 static jmethodID    gInputStream_readMethodID;
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 1c2d13d..95bcbd6 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "Minikin"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <core_jni_helpers.h>
 
 #include "SkData.h"
@@ -24,8 +24,8 @@
 #include "SkRefCnt.h"
 #include "SkTypeface.h"
 #include "GraphicsJNI.h"
-#include <ScopedPrimitiveArray.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_util_AssetManager.h>
 #include <androidfw/AssetManager.h>
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 889a3db..19ca1ea 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -4,7 +4,7 @@
 #include <sys/mman.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "GraphicsJNI.h"
 
 #include "SkCanvas.h"
diff --git a/core/jni/android/graphics/Movie.cpp b/core/jni/android/graphics/Movie.cpp
index 71988f9c..c96552f 100644
--- a/core/jni/android/graphics/Movie.cpp
+++ b/core/jni/android/graphics/Movie.cpp
@@ -1,6 +1,6 @@
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "GraphicsJNI.h"
-#include "ScopedLocalRef.h"
+#include <nativehelper/ScopedLocalRef.h>
 #include "SkFrontBufferedStream.h"
 #include "SkMovie.h"
 #include "SkStream.h"
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 4f2f389..fa9c9c7 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -31,7 +31,7 @@
 
 #include "utils/NinePatch.h"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 using namespace android;
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 85092ad..fdd4b3c 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -22,8 +22,8 @@
 #include "jni.h"
 #include "GraphicsJNI.h"
 #include "core_jni_helpers.h"
-#include <ScopedStringChars.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedStringChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 #include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 9a53cad..1799e01 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -18,7 +18,7 @@
 #include "core_jni_helpers.h"
 
 #include "GraphicsJNI.h"
-#include "ScopedPrimitiveArray.h"
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include "SkTypeface.h"
 #include <android_runtime/android_util_AssetManager.h>
 #include <androidfw/AssetManager.h>
diff --git a/core/jni/android/graphics/pdf/PdfEditor.cpp b/core/jni/android/graphics/pdf/PdfEditor.cpp
index 59c5be6..8c9f8fa 100644
--- a/core/jni/android/graphics/pdf/PdfEditor.cpp
+++ b/core/jni/android/graphics/pdf/PdfEditor.cpp
@@ -24,7 +24,7 @@
 #include <utils/Log.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
diff --git a/core/jni/android/graphics/pdf/PdfRenderer.cpp b/core/jni/android/graphics/pdf/PdfRenderer.cpp
index 43550ac..e8b2aa9 100644
--- a/core/jni/android/graphics/pdf/PdfRenderer.cpp
+++ b/core/jni/android/graphics/pdf/PdfRenderer.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "GraphicsJNI.h"
 #include "SkBitmap.h"
 #include "SkMatrix.h"
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 41d9111..825c19f 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "GraphicsJNI.h"
 
 #include <math.h>
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index fd9e714..6d0619f 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -35,7 +35,7 @@
 
 #include <utils/Looper.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_os_MessageQueue.h"
 #include "android_view_InputChannel.h"
 #include "android_view_KeyEvent.h"
@@ -45,7 +45,7 @@
 
 #include "core_jni_helpers.h"
 
-#include "ScopedUtfChars.h"
+#include <nativehelper/ScopedUtfChars.h>
 
 #define LOG_TRACE(...)
 //#define LOG_TRACE(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
diff --git a/core/jni/android_app_admin_SecurityLog.cpp b/core/jni/android_app_admin_SecurityLog.cpp
index e8ca793..5c45b4b 100644
--- a/core/jni/android_app_admin_SecurityLog.cpp
+++ b/core/jni/android_app_admin_SecurityLog.cpp
@@ -16,7 +16,7 @@
 
 #include <fcntl.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 #include "jni.h"
 #include <private/android_logger.h>
diff --git a/core/jni/android_app_backup_FullBackup.cpp b/core/jni/android_app_backup_FullBackup.cpp
index 63b2e2a..3e39989 100644
--- a/core/jni/android_app_backup_FullBackup.cpp
+++ b/core/jni/android_app_backup_FullBackup.cpp
@@ -20,7 +20,7 @@
 #include <utils/Log.h>
 #include <utils/String8.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 
 #include <androidfw/BackupHelpers.h>
diff --git a/core/jni/android_backup_BackupDataInput.cpp b/core/jni/android_backup_BackupDataInput.cpp
index 096a784..aa8acc16 100644
--- a/core/jni/android_backup_BackupDataInput.cpp
+++ b/core/jni/android_backup_BackupDataInput.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "FileBackupHelper_native"
 #include <utils/Log.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 
 #include <androidfw/BackupHelpers.h>
diff --git a/core/jni/android_backup_BackupDataOutput.cpp b/core/jni/android_backup_BackupDataOutput.cpp
index a7894f4..4f5d1f80 100644
--- a/core/jni/android_backup_BackupDataOutput.cpp
+++ b/core/jni/android_backup_BackupDataOutput.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "FileBackupHelper_native"
 #include <utils/Log.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <androidfw/BackupHelpers.h>
diff --git a/core/jni/android_backup_BackupHelperDispatcher.cpp b/core/jni/android_backup_BackupHelperDispatcher.cpp
index 80bdaf8..fac7eba 100644
--- a/core/jni/android_backup_BackupHelperDispatcher.cpp
+++ b/core/jni/android_backup_BackupHelperDispatcher.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "BackupHelperDispatcher_native"
 #include <utils/Log.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 
 #include <sys/types.h>
diff --git a/core/jni/android_backup_FileBackupHelperBase.cpp b/core/jni/android_backup_FileBackupHelperBase.cpp
index 6d6ac1b..65840ee 100644
--- a/core/jni/android_backup_FileBackupHelperBase.cpp
+++ b/core/jni/android_backup_FileBackupHelperBase.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "FileBackupHelper_native"
 #include <utils/Log.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <androidfw/BackupHelpers.h>
diff --git a/core/jni/android_content_res_ObbScanner.cpp b/core/jni/android_content_res_ObbScanner.cpp
index 36d78cf..de429a0 100644
--- a/core/jni/android_content_res_ObbScanner.cpp
+++ b/core/jni/android_content_res_ObbScanner.cpp
@@ -21,7 +21,7 @@
 #include <androidfw/ObbFile.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "utils/misc.h"
 #include "android_runtime/AndroidRuntime.h"
 
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 3fc3aaf..7c1d313 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -20,7 +20,7 @@
 
 #include <inttypes.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 
 #include <utils/Log.h>
diff --git a/core/jni/android_database_SQLiteCommon.h b/core/jni/android_database_SQLiteCommon.h
index 0cac176..81164ef 100644
--- a/core/jni/android_database_SQLiteCommon.h
+++ b/core/jni/android_database_SQLiteCommon.h
@@ -18,7 +18,7 @@
 #define _ANDROID_DATABASE_SQLITE_COMMON_H
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 #include <sqlite3.h>
 
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index bcc3bb0..cbf09bd 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "SQLiteConnection"
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 
diff --git a/core/jni/android_database_SQLiteDebug.cpp b/core/jni/android_database_SQLiteDebug.cpp
index 4e4c36c..3ba9b91 100644
--- a/core/jni/android_database_SQLiteDebug.cpp
+++ b/core/jni/android_database_SQLiteDebug.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "SQLiteDebug"
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 
 #include <stdio.h>
diff --git a/core/jni/android_database_SQLiteGlobal.cpp b/core/jni/android_database_SQLiteGlobal.cpp
index 03e2387..ec5dc4a 100644
--- a/core/jni/android_database_SQLiteGlobal.cpp
+++ b/core/jni/android_database_SQLiteGlobal.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "SQLiteGlobal"
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <sqlite3.h>
diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
index 3e7a04e..2f25d8f 100644
--- a/core/jni/android_ddm_DdmHandleNativeHeap.cpp
+++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
@@ -18,7 +18,7 @@
 #undef LOG_TAG
 #define LOG_TAG "DdmHandleNativeHeap"
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <jni.h>
 #include "core_jni_helpers.h"
 
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index b926270..fb9494f 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -20,7 +20,7 @@
 #include <utils/Log.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 #include <android_runtime/android_graphics_SurfaceTexture.h>
 #include <android_runtime/android_view_Surface.h>
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 271f24b..7adcc9b 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -15,13 +15,13 @@
  */
 #define LOG_TAG "SensorManager"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_os_MessageQueue.h"
 #include "core_jni_helpers.h"
 #include "jni.h"
 
-#include <ScopedUtfChars.h>
-#include <ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/ScopedLocalRef.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <gui/Sensor.h>
 #include <gui/SensorEventQueue.h>
diff --git a/core/jni/android_hardware_SerialPort.cpp b/core/jni/android_hardware_SerialPort.cpp
index 393dc7b..190ddce 100644
--- a/core/jni/android_hardware_SerialPort.cpp
+++ b/core/jni/android_hardware_SerialPort.cpp
@@ -19,7 +19,7 @@
 #include "utils/Log.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <stdio.h>
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 0c7f5a1..9dbb8d7 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -20,7 +20,7 @@
 #include <utils/Log.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 #include <system/sound_trigger.h>
 #include <soundtrigger/SoundTriggerCallback.h>
diff --git a/core/jni/android_hardware_UsbDevice.cpp b/core/jni/android_hardware_UsbDevice.cpp
index 89d33e2..879d409 100644
--- a/core/jni/android_hardware_UsbDevice.cpp
+++ b/core/jni/android_hardware_UsbDevice.cpp
@@ -19,7 +19,7 @@
 #include "utils/Log.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <usbhost/usbhost.h>
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index 1ba9fc5..c15cb83 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -19,7 +19,7 @@
 #include "utils/Log.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <usbhost/usbhost.h>
diff --git a/core/jni/android_hardware_UsbRequest.cpp b/core/jni/android_hardware_UsbRequest.cpp
index 399e7b1..06cfd4c 100644
--- a/core/jni/android_hardware_UsbRequest.cpp
+++ b/core/jni/android_hardware_UsbRequest.cpp
@@ -19,7 +19,7 @@
 #include "utils/Log.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <usbhost/usbhost.h>
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 518f99e..45e7c07 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -28,7 +28,7 @@
 #include <vector>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_os_Parcel.h"
 #include "core_jni_helpers.h"
 #include "android_runtime/android_hardware_camera2_CameraMetadata.h"
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index d2932e43..09aaff6 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -45,7 +45,7 @@
 #include "android_runtime/android_hardware_camera2_CameraMetadata.h"
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 using namespace android;
 using namespace img_utils;
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 80f9d57..33ce3aa 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -22,7 +22,7 @@
 #include <camera/CameraUtils.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/android_graphics_SurfaceTexture.h"
diff --git a/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp b/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
index a081665..fac243a 100644
--- a/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
+++ b/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
@@ -21,7 +21,7 @@
 #include <utils/Vector.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <ui/GraphicBuffer.h>
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
index 44a3555..0e2b80e 100644
--- a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "ActivityRecognitionHardware"
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 5e93fc9..e4da3c6 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -20,13 +20,13 @@
 
 #include <inttypes.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <utils/Log.h>
 #include <media/AudioRecord.h>
 
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 #include "android_media_AudioFormat.h"
 #include "android_media_AudioErrors.h"
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index d30e6eb..6c3d5f1 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -22,7 +22,7 @@
 
 #include <sstream>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <media/AudioSystem.h>
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 86c4df7..e1470c3 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -19,11 +19,11 @@
 
 #include "android_media_AudioTrack.h"
 
-#include <JNIHelp.h>
-#include <JniConstants.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/JniConstants.h>
 #include "core_jni_helpers.h"
 
-#include "ScopedBytes.h"
+#include <nativehelper/ScopedBytes.h>
 
 #include <utils/Log.h>
 #include <media/AudioSystem.h>
diff --git a/core/jni/android_media_DeviceCallback.cpp b/core/jni/android_media_DeviceCallback.cpp
index e159373..108fa00 100644
--- a/core/jni/android_media_DeviceCallback.cpp
+++ b/core/jni/android_media_DeviceCallback.cpp
@@ -19,8 +19,8 @@
 #define LOG_TAG "AudioDeviceCallback-JNI"
 
 #include <utils/Log.h>
-#include <JNIHelp.h>
-#include <JniConstants.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/JniConstants.h>
 #include "core_jni_helpers.h"
 #include <media/AudioSystem.h>
 
diff --git a/core/jni/android_media_JetPlayer.cpp b/core/jni/android_media_JetPlayer.cpp
index 873c3f2..da116bf 100644
--- a/core/jni/android_media_JetPlayer.cpp
+++ b/core/jni/android_media_JetPlayer.cpp
@@ -23,7 +23,7 @@
 #include <fcntl.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <utils/Log.h>
diff --git a/core/jni/android_media_RemoteDisplay.cpp b/core/jni/android_media_RemoteDisplay.cpp
index bd1a6ec..3b517f1 100644
--- a/core/jni/android_media_RemoteDisplay.cpp
+++ b/core/jni/android_media_RemoteDisplay.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "RemoteDisplay"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include "android_os_Parcel.h"
 #include "android_util_Binder.h"
@@ -36,7 +36,7 @@
 
 #include <utils/Log.h>
 
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 namespace android {
 
diff --git a/core/jni/android_media_ToneGenerator.cpp b/core/jni/android_media_ToneGenerator.cpp
index aec6263..ecb3cd6 100644
--- a/core/jni/android_media_ToneGenerator.cpp
+++ b/core/jni/android_media_ToneGenerator.cpp
@@ -22,7 +22,7 @@
 #include <fcntl.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <utils/Log.h>
diff --git a/core/jni/android_net_LocalSocketImpl.cpp b/core/jni/android_net_LocalSocketImpl.cpp
index 37b6df1..6df23f7 100644
--- a/core/jni/android_net_LocalSocketImpl.cpp
+++ b/core/jni/android_net_LocalSocketImpl.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "LocalSocketImpl"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include "utils/Log.h"
 #include "utils/misc.h"
@@ -35,7 +35,7 @@
 
 #include <cutils/sockets.h>
 #include <netinet/tcp.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 namespace android {
 
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 3e99521..823f1cc 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "NetUtils"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "NetdClient.h"
 #include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
diff --git a/core/jni/android_net_TrafficStats.cpp b/core/jni/android_net_TrafficStats.cpp
index 7b7d0cf..d0c237d 100644
--- a/core/jni/android_net_TrafficStats.cpp
+++ b/core/jni/android_net_TrafficStats.cpp
@@ -25,7 +25,7 @@
 
 #include "core_jni_helpers.h"
 #include <jni.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <utils/misc.h>
 #include <utils/Log.h>
 
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index c8b1784..6163588 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -21,7 +21,7 @@
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/android_graphics_SurfaceTexture.h>
diff --git a/core/jni/android_opengl_EGLExt.cpp b/core/jni/android_opengl_EGLExt.cpp
index 62ccad4..df1aa20 100644
--- a/core/jni/android_opengl_EGLExt.cpp
+++ b/core/jni/android_opengl_EGLExt.cpp
@@ -21,7 +21,7 @@
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/android_graphics_SurfaceTexture.h>
diff --git a/core/jni/android_opengl_GLES10.cpp b/core/jni/android_opengl_GLES10.cpp
index f4135c2..b93ae80 100644
--- a/core/jni/android_opengl_GLES10.cpp
+++ b/core/jni/android_opengl_GLES10.cpp
@@ -25,7 +25,7 @@
 #include <GLES/glext.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <assert.h>
diff --git a/core/jni/android_opengl_GLES10Ext.cpp b/core/jni/android_opengl_GLES10Ext.cpp
index 4dc4233..26a01f2 100644
--- a/core/jni/android_opengl_GLES10Ext.cpp
+++ b/core/jni/android_opengl_GLES10Ext.cpp
@@ -25,7 +25,7 @@
 #include <GLES/glext.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <assert.h>
diff --git a/core/jni/android_opengl_GLES11.cpp b/core/jni/android_opengl_GLES11.cpp
index 2625e03..0762d6e 100644
--- a/core/jni/android_opengl_GLES11.cpp
+++ b/core/jni/android_opengl_GLES11.cpp
@@ -25,7 +25,7 @@
 #include <GLES/glext.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <assert.h>
diff --git a/core/jni/android_opengl_GLES11Ext.cpp b/core/jni/android_opengl_GLES11Ext.cpp
index fb85cb0..3006558 100644
--- a/core/jni/android_opengl_GLES11Ext.cpp
+++ b/core/jni/android_opengl_GLES11Ext.cpp
@@ -25,7 +25,7 @@
 #include <GLES/glext.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <assert.h>
diff --git a/core/jni/android_opengl_GLES20.cpp b/core/jni/android_opengl_GLES20.cpp
index ac3bf7a..c5787f4 100644
--- a/core/jni/android_opengl_GLES20.cpp
+++ b/core/jni/android_opengl_GLES20.cpp
@@ -25,7 +25,7 @@
 #include <GLES2/gl2ext.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <assert.h>
diff --git a/core/jni/android_opengl_GLES30.cpp b/core/jni/android_opengl_GLES30.cpp
index 59b8911..d1e6161 100644
--- a/core/jni/android_opengl_GLES30.cpp
+++ b/core/jni/android_opengl_GLES30.cpp
@@ -25,7 +25,7 @@
 #include <GLES3/gl3ext.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <assert.h>
diff --git a/core/jni/android_opengl_GLES31.cpp b/core/jni/android_opengl_GLES31.cpp
index 156e7bd..ab64757 100644
--- a/core/jni/android_opengl_GLES31.cpp
+++ b/core/jni/android_opengl_GLES31.cpp
@@ -23,7 +23,7 @@
 #include <stdint.h>
 #include <GLES3/gl31.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <assert.h>
diff --git a/core/jni/android_opengl_GLES31Ext.cpp b/core/jni/android_opengl_GLES31Ext.cpp
index 5be7be0..da45fef 100644
--- a/core/jni/android_opengl_GLES31Ext.cpp
+++ b/core/jni/android_opengl_GLES31Ext.cpp
@@ -24,7 +24,7 @@
 #include <GLES2/gl2ext.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <assert.h>
diff --git a/core/jni/android_opengl_GLES32.cpp b/core/jni/android_opengl_GLES32.cpp
index f9a1a8e..824a2e8 100644
--- a/core/jni/android_opengl_GLES32.cpp
+++ b/core/jni/android_opengl_GLES32.cpp
@@ -23,7 +23,7 @@
 #include <stdint.h>
 #include <GLES3/gl32.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <assert.h>
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 821d0e5..12096239 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -75,14 +75,27 @@
     HEAP_GL,
     HEAP_OTHER_MEMTRACK,
 
+    // Dalvik extra sections (heap).
     HEAP_DALVIK_NORMAL,
     HEAP_DALVIK_LARGE,
-    HEAP_DALVIK_LINEARALLOC,
-    HEAP_DALVIK_ACCOUNTING,
-    HEAP_DALVIK_CODE_CACHE,
     HEAP_DALVIK_ZYGOTE,
     HEAP_DALVIK_NON_MOVING,
-    HEAP_DALVIK_INDIRECT_REFERENCE_TABLE,
+
+    // Dalvik other extra sections.
+    HEAP_DALVIK_OTHER_LINEARALLOC,
+    HEAP_DALVIK_OTHER_ACCOUNTING,
+    HEAP_DALVIK_OTHER_CODE_CACHE,
+    HEAP_DALVIK_OTHER_COMPILER_METADATA,
+    HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE,
+
+    // Boot vdex / app dex / app vdex
+    HEAP_DEX_BOOT_VDEX,
+    HEAP_DEX_APP_DEX,
+    HEAP_DEX_APP_VDEX,
+
+    // App art, boot art.
+    HEAP_ART_APP,
+    HEAP_ART_BOOT,
 
     _NUM_HEAP,
     _NUM_EXCLUSIVE_HEAP = HEAP_OTHER_MEMTRACK+1,
@@ -297,15 +310,30 @@
                 whichHeap = HEAP_TTF;
                 is_swappable = true;
             } else if ((nameLen > 4 && strstr(name, ".dex") != NULL) ||
-                       (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0) ||
-                       (nameLen > 5 && strcmp(name+nameLen-5, ".vdex") == 0)) {
+                       (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0)) {
                 whichHeap = HEAP_DEX;
+                subHeap = HEAP_DEX_APP_DEX;
+                is_swappable = true;
+            } else if (nameLen > 5 && strcmp(name+nameLen-5, ".vdex") == 0) {
+                whichHeap = HEAP_DEX;
+                // Handle system@framework@boot* and system/framework/boot*
+                if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
+                    subHeap = HEAP_DEX_BOOT_VDEX;
+                } else {
+                    subHeap = HEAP_DEX_APP_VDEX;
+                }
                 is_swappable = true;
             } else if (nameLen > 4 && strcmp(name+nameLen-4, ".oat") == 0) {
                 whichHeap = HEAP_OAT;
                 is_swappable = true;
             } else if (nameLen > 4 && strcmp(name+nameLen-4, ".art") == 0) {
                 whichHeap = HEAP_ART;
+                // Handle system@framework@boot* and system/framework/boot*
+                if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) {
+                    subHeap = HEAP_ART_BOOT;
+                } else {
+                    subHeap = HEAP_ART_APP;
+                }
                 is_swappable = true;
             } else if (strncmp(name, "/dev/", 5) == 0) {
                 if (strncmp(name, "/dev/kgsl-3d0", 13) == 0) {
@@ -314,7 +342,7 @@
                     if (strncmp(name, "/dev/ashmem/dalvik-", 19) == 0) {
                         whichHeap = HEAP_DALVIK_OTHER;
                         if (strstr(name, "/dev/ashmem/dalvik-LinearAlloc") == name) {
-                            subHeap = HEAP_DALVIK_LINEARALLOC;
+                            subHeap = HEAP_DALVIK_OTHER_LINEARALLOC;
                         } else if ((strstr(name, "/dev/ashmem/dalvik-alloc space") == name) ||
                                    (strstr(name, "/dev/ashmem/dalvik-main space") == name)) {
                             // This is the regular Dalvik heap.
@@ -332,13 +360,14 @@
                             whichHeap = HEAP_DALVIK;
                             subHeap = HEAP_DALVIK_ZYGOTE;
                         } else if (strstr(name, "/dev/ashmem/dalvik-indirect ref") == name) {
-                            subHeap = HEAP_DALVIK_INDIRECT_REFERENCE_TABLE;
+                            subHeap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE;
                         } else if (strstr(name, "/dev/ashmem/dalvik-jit-code-cache") == name ||
-                                   strstr(name, "/dev/ashmem/dalvik-data-code-cache") == name ||
-                                   strstr(name, "/dev/ashmem/dalvik-CompilerMetadata") == name) {
-                            subHeap = HEAP_DALVIK_CODE_CACHE;
+                                   strstr(name, "/dev/ashmem/dalvik-data-code-cache") == name) {
+                            subHeap = HEAP_DALVIK_OTHER_CODE_CACHE;
+                        } else if (strstr(name, "/dev/ashmem/dalvik-CompilerMetadata") == name) {
+                            subHeap = HEAP_DALVIK_OTHER_COMPILER_METADATA;
                         } else {
-                            subHeap = HEAP_DALVIK_ACCOUNTING;  // Default to accounting.
+                            subHeap = HEAP_DALVIK_OTHER_ACCOUNTING;  // Default to accounting.
                         }
                     } else if (strncmp(name, "/dev/ashmem/CursorWindow", 24) == 0) {
                         whichHeap = HEAP_CURSOR;
@@ -423,7 +452,8 @@
             stats[whichHeap].sharedClean += shared_clean;
             stats[whichHeap].swappedOut += swapped_out;
             stats[whichHeap].swappedOutPss += swapped_out_pss;
-            if (whichHeap == HEAP_DALVIK || whichHeap == HEAP_DALVIK_OTHER) {
+            if (whichHeap == HEAP_DALVIK || whichHeap == HEAP_DALVIK_OTHER ||
+                    whichHeap == HEAP_DEX || whichHeap == HEAP_ART) {
                 stats[subHeap].pss += pss;
                 stats[subHeap].swappablePss += swappable_pss;
                 stats[subHeap].privateDirty += private_dirty;
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index a271aad..eaa7904 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -25,7 +25,7 @@
 
 #include <cstring>
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
 #include <android/hidl/base/1.0/IBase.h>
 #include <android/hidl/base/1.0/BpHwBase.h>
@@ -36,12 +36,15 @@
 #include <hwbinder/ProcessState.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <vintf/parse_string.h>
+#include <utils/misc.h>
 
 #include "core_jni_helpers.h"
 
 using android::AndroidRuntime;
 using android::hardware::hidl_vec;
 using android::hardware::hidl_string;
+using android::hardware::IPCThreadState;
+using android::hardware::ProcessState;
 template<typename T>
 using Return = android::hardware::Return<T>;
 
@@ -368,7 +371,7 @@
     if (transport != IServiceManager::Transport::HWBINDER && !vintfLegacy) {
         LOG(ERROR) << "service " << ifaceName << " declares transport method "
                    << toString(transport) << " but framework expects hwbinder.";
-        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
+        signalExceptionForError(env, NAME_NOT_FOUND, true /* canThrowRemoteException */);
         return NULL;
     }
 
@@ -379,8 +382,7 @@
         return NULL;
     }
 
-    sp<hardware::IBinder> service = hardware::toBinder<
-            hidl::base::V1_0::IBase, hidl::base::V1_0::BpHwBase>(ret);
+    sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret);
 
     if (service == NULL) {
         signalExceptionForError(env, NAME_NOT_FOUND);
@@ -393,6 +395,20 @@
     return JHwRemoteBinder::NewObject(env, service);
 }
 
+void JHwBinder_native_configureRpcThreadpool(jlong maxThreads, jboolean callerWillJoin) {
+    CHECK(maxThreads > 0);
+    ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/);
+}
+
+void JHwBinder_native_joinRpcThreadpool() {
+    IPCThreadState::self()->joinThreadPool();
+}
+
+static void JHwBinder_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/)
+{
+    report_sysprop_change();
+}
+
 static JNINativeMethod gMethods[] = {
     { "native_init", "()J", (void *)JHwBinder_native_init },
     { "native_setup", "()V", (void *)JHwBinder_native_setup },
@@ -406,6 +422,15 @@
 
     { "getService", "(Ljava/lang/String;Ljava/lang/String;)L" PACKAGE_PATH "/IHwBinder;",
         (void *)JHwBinder_native_getService },
+
+    { "configureRpcThreadpool", "(JZ)V",
+        (void *)JHwBinder_native_configureRpcThreadpool },
+
+    { "joinRpcThreadpool", "()V",
+        (void *)JHwBinder_native_joinRpcThreadpool },
+
+    { "native_report_sysprop_change", "()V",
+        (void *)JHwBinder_report_sysprop_change },
 };
 
 namespace android {
diff --git a/core/jni/android_os_HwBlob.cpp b/core/jni/android_os_HwBlob.cpp
index 8590ecf..0c23797 100644
--- a/core/jni/android_os_HwBlob.cpp
+++ b/core/jni/android_os_HwBlob.cpp
@@ -22,7 +22,7 @@
 
 #include "android_os_HwParcel.h"
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <hidl/Status.h>
 #include <nativehelper/ScopedLocalRef.h>
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 6ea809a..b412b6a 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -24,7 +24,7 @@
 #include "android_os_HwBlob.h"
 #include "android_os_HwRemoteBinder.h"
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <hidl/HidlTransportSupport.h>
 #include <hidl/Status.h>
diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp
index 9c2ee9c..cf59a56a 100644
--- a/core/jni/android_os_HwRemoteBinder.cpp
+++ b/core/jni/android_os_HwRemoteBinder.cpp
@@ -22,10 +22,10 @@
 
 #include "android_os_HwParcel.h"
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <hidl/Status.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedLocalRef.h>
 
 #include "core_jni_helpers.h"
diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp
index c198a73..19926e2 100644
--- a/core/jni/android_os_MemoryFile.cpp
+++ b/core/jni/android_os_MemoryFile.cpp
@@ -19,7 +19,7 @@
 
 #include <cutils/ashmem.h>
 #include "core_jni_helpers.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <unistd.h>
 #include <sys/mman.h>
 
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index e57a719..f7a98d1 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "MessageQueue-JNI"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 
 #include <utils/Looper.h>
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 0a8ae2b..6af5b96 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -20,7 +20,7 @@
 #include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <fcntl.h>
 #include <stdio.h>
@@ -41,8 +41,8 @@
 #include <utils/threads.h>
 #include <utils/String8.h>
 
-#include <ScopedUtfChars.h>
-#include <ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/ScopedLocalRef.h>
 
 #include <android_runtime/AndroidRuntime.h>
 
diff --git a/core/jni/android_os_SELinux.cpp b/core/jni/android_os_SELinux.cpp
index 4b68c0d..6778b29 100644
--- a/core/jni/android_os_SELinux.cpp
+++ b/core/jni/android_os_SELinux.cpp
@@ -17,15 +17,15 @@
 #define LOG_TAG "SELinuxJNI"
 #include <utils/Log.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include "core_jni_helpers.h"
 #include "selinux/selinux.h"
 #include "selinux/android.h"
 #include <errno.h>
 #include <memory>
-#include <ScopedLocalRef.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 namespace android {
 
diff --git a/core/jni/android_os_SystemClock.cpp b/core/jni/android_os_SystemClock.cpp
index d98407d..27a4034 100644
--- a/core/jni/android_os_SystemClock.cpp
+++ b/core/jni/android_os_SystemClock.cpp
@@ -25,7 +25,7 @@
 #include <errno.h>
 #include <string.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include "core_jni_helpers.h"
 
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index dc5ce39..25182e1 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -23,9 +23,9 @@
 #include <utils/String8.h>
 #include <log/log.h>
 
-#include <JNIHelp.h>
-#include <ScopedUtfChars.h>
-#include <ScopedStringChars.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/ScopedStringChars.h>
 
 namespace android {
 
diff --git a/core/jni/android_os_UEventObserver.cpp b/core/jni/android_os_UEventObserver.cpp
index 30d40a2..2df74b0 100644
--- a/core/jni/android_os_UEventObserver.cpp
+++ b/core/jni/android_os_UEventObserver.cpp
@@ -21,13 +21,13 @@
 
 #include "hardware_legacy/uevent.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <utils/Mutex.h>
 #include <utils/Vector.h>
 #include <utils/String8.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 namespace android {
 
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index fa9379e..7ec4b8e 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -21,7 +21,7 @@
 #include <vector>
 #include <string>
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <vintf/VintfObject.h>
 #include <vintf/parse_string.h>
 #include <vintf/parse_xml.h>
@@ -102,7 +102,10 @@
         cPackageInfo[i] = cString;
         env->ReleaseStringUTFChars(element, cString);
     }
-    int32_t status = VintfObject::CheckCompatibility(cPackageInfo);
+    std::string error;
+    int32_t status = VintfObject::CheckCompatibility(cPackageInfo, &error);
+    if (status)
+        LOG(WARNING) << "VintfObject.verify() returns " << status << ": " << error;
     return status;
 }
 
diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp
index ecb6854..19220cf0 100644
--- a/core/jni/android_os_VintfRuntimeInfo.cpp
+++ b/core/jni/android_os_VintfRuntimeInfo.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "VintfRuntimeInfo"
 //#define LOG_NDEBUG 0
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <vintf/VintfObject.h>
 #include <vintf/parse_string.h>
 #include <vintf/parse_xml.h>
diff --git a/core/jni/android_os_seccomp.cpp b/core/jni/android_os_seccomp.cpp
index 4502371..06e2a16 100644
--- a/core/jni/android_os_seccomp.cpp
+++ b/core/jni/android_os_seccomp.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "core_jni_helpers.h"
-#include "JniConstants.h"
+#include <nativehelper/JniConstants.h>
 #include "utils/Log.h"
 #include <selinux/selinux.h>
 
diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp
index 818bf53..58295af 100644
--- a/core/jni/android_server_NetworkManagementSocketTagger.cpp
+++ b/core/jni/android_server_NetworkManagementSocketTagger.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "NMST_QTagUidNative"
 #include <utils/Log.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include "jni.h"
 #include <utils/misc.h>
diff --git a/core/jni/android_server_Watchdog.cpp b/core/jni/android_server_Watchdog.cpp
index d1f9434..01d565b 100644
--- a/core/jni/android_server_Watchdog.cpp
+++ b/core/jni/android_server_Watchdog.cpp
@@ -25,7 +25,7 @@
 #include <errno.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 static void dumpOneStack(int tid, int outFd) {
diff --git a/core/jni/android_text_AndroidBidi.cpp b/core/jni/android_text_AndroidBidi.cpp
index 2a3f036..6e9a339 100644
--- a/core/jni/android_text_AndroidBidi.cpp
+++ b/core/jni/android_text_AndroidBidi.cpp
@@ -17,7 +17,7 @@
 
 #define LOG_TAG "AndroidUnicode"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 #include "utils/misc.h"
 #include "utils/Log.h"
@@ -38,7 +38,7 @@
         if (info != NULL) {
             UErrorCode status = U_ZERO_ERROR;
             UBiDi* bidi = ubidi_openSized(n, 0, &status);
-            ubidi_setPara(bidi, chs, n, dir, NULL, &status);
+            ubidi_setPara(bidi, reinterpret_cast<const UChar*>(chs), n, dir, NULL, &status);
             if (U_SUCCESS(status)) {
                 for (int i = 0; i < n; ++i) {
                   info[i] = ubidi_getLevelAt(bidi, i);
diff --git a/core/jni/android_text_AndroidCharacter.cpp b/core/jni/android_text_AndroidCharacter.cpp
index 474a74e..8885aac 100644
--- a/core/jni/android_text_AndroidCharacter.cpp
+++ b/core/jni/android_text_AndroidCharacter.cpp
@@ -17,8 +17,8 @@
 
 #define LOG_TAG "AndroidUnicode"
 
-#include "JNIHelp.h"
-#include "ScopedPrimitiveArray.h"
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include "core_jni_helpers.h"
 #include "utils/misc.h"
 #include "utils/Log.h"
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 13e4f1a..fd10675 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -21,9 +21,9 @@
 #include "unicode/brkiter.h"
 #include "utils/misc.h"
 #include "utils/Log.h"
-#include "ScopedStringChars.h"
-#include "ScopedPrimitiveArray.h"
-#include "JNIHelp.h"
+#include <nativehelper/ScopedStringChars.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 #include <cstdint>
 #include <vector>
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 171bd89..15966ad 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -37,9 +37,9 @@
 #include "android_util_Binder.h"
 #include "core_jni_helpers.h"
 #include "jni.h"
-#include "JNIHelp.h"
-#include "ScopedStringChars.h"
-#include "ScopedUtfChars.h"
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedStringChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "utils/Log.h"
 #include "utils/misc.h"
 #include "utils/String8.h"
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index e2aa17b..5b0f776 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -20,7 +20,7 @@
 #include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <fcntl.h>
 #include <inttypes.h>
@@ -44,8 +44,8 @@
 #include <utils/SystemClock.h>
 #include <utils/threads.h>
 
-#include <ScopedUtfChars.h>
-#include <ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/ScopedLocalRef.h>
 
 #include "core_jni_helpers.h"
 
diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp
index 0b4fbcc..3d05778 100644
--- a/core/jni/android_util_EventLog.cpp
+++ b/core/jni/android_util_EventLog.cpp
@@ -20,7 +20,7 @@
 
 #include <log/log.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 #include "jni.h"
 
diff --git a/core/jni/android_util_FileObserver.cpp b/core/jni/android_util_FileObserver.cpp
index 2b93b6d..6f975b2 100644
--- a/core/jni/android_util_FileObserver.cpp
+++ b/core/jni/android_util_FileObserver.cpp
@@ -15,7 +15,7 @@
 ** limitations under the License.
 */
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include "utils/Log.h"
 #include "utils/misc.h"
diff --git a/core/jni/android_util_Log.cpp b/core/jni/android_util_Log.cpp
index 56505af..a6adc88 100644
--- a/core/jni/android_util_Log.cpp
+++ b/core/jni/android_util_Log.cpp
@@ -26,7 +26,7 @@
 #include <utils/String8.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "utils/misc.h"
 #include "core_jni_helpers.h"
 #include "android_util_Log.h"
diff --git a/core/jni/android_util_Log.h b/core/jni/android_util_Log.h
index 4804a854..8a32864 100644
--- a/core/jni/android_util_Log.h
+++ b/core/jni/android_util_Log.h
@@ -18,7 +18,7 @@
 #define _ANDROID_UTIL_LOG_H
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 
 namespace android {
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index a03d3c5..cd95432 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -30,7 +30,7 @@
 #include "core_jni_helpers.h"
 
 #include "android_util_Binder.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <dirent.h>
 #include <fcntl.h>
diff --git a/core/jni/android_util_StringBlock.cpp b/core/jni/android_util_StringBlock.cpp
index b396afe..760f9e3 100644
--- a/core/jni/android_util_StringBlock.cpp
+++ b/core/jni/android_util_StringBlock.cpp
@@ -18,7 +18,7 @@
 #define LOG_TAG "StringBlock"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <utils/misc.h>
 #include <core_jni_helpers.h>
 #include <utils/Log.h>
diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp
index a15c23c..87f8851 100644
--- a/core/jni/android_util_XmlBlock.cpp
+++ b/core/jni/android_util_XmlBlock.cpp
@@ -18,7 +18,7 @@
 #define LOG_TAG "XmlBlock"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <core_jni_helpers.h>
 #include <androidfw/AssetManager.h>
 #include <androidfw/ResourceTypes.h>
diff --git a/core/jni/android_util_jar_StrictJarFile.cpp b/core/jni/android_util_jar_StrictJarFile.cpp
index 4f1f926..4ab8db4 100644
--- a/core/jni/android_util_jar_StrictJarFile.cpp
+++ b/core/jni/android_util_jar_StrictJarFile.cpp
@@ -22,10 +22,10 @@
 
 #include <log/log.h>
 
-#include "JNIHelp.h"
-#include "JniConstants.h"
-#include "ScopedLocalRef.h"
-#include "ScopedUtfChars.h"
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/JniConstants.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "jni.h"
 #include "ziparchive/zip_archive.h"
 
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index ea5a760..78bf1db 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -18,7 +18,7 @@
 
 //#define LOG_NDEBUG 0
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <inttypes.h>
 
@@ -30,7 +30,7 @@
 #include <gui/DisplayEventReceiver.h>
 #include "android_os_MessageQueue.h"
 
-#include <ScopedLocalRef.h>
+#include <nativehelper/ScopedLocalRef.h>
 
 #include "core_jni_helpers.h"
 
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 1c6ead0..71742cb 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "InputChannel-JNI"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <android_runtime/AndroidRuntime.h>
 #include <binder/Parcel.h>
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 9cf6a9d..494fad7 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -17,10 +17,10 @@
 #include <input/Input.h>
 
 #include <android_runtime/AndroidRuntime.h>
-#include <nativehelper/jni.h>
+#include <jni.h>
 #include <nativehelper/JNIHelp.h>
 
-#include <ScopedLocalRef.h>
+#include <nativehelper/ScopedLocalRef.h>
 
 #include "android_view_InputDevice.h"
 #include "android_view_KeyCharacterMap.h"
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 8293cd8..31e954b 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -18,7 +18,7 @@
 
 //#define LOG_NDEBUG 0
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
@@ -31,7 +31,7 @@
 #include "android_view_KeyEvent.h"
 #include "android_view_MotionEvent.h"
 
-#include <ScopedLocalRef.h>
+#include <nativehelper/ScopedLocalRef.h>
 
 #include "core_jni_helpers.h"
 
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 3bd6917..420ff2a 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -18,7 +18,7 @@
 
 //#define LOG_NDEBUG 0
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
@@ -31,7 +31,7 @@
 #include "android_view_KeyEvent.h"
 #include "android_view_MotionEvent.h"
 
-#include <ScopedLocalRef.h>
+#include <nativehelper/ScopedLocalRef.h>
 
 #include "core_jni_helpers.h"
 
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index 96ccdee..24c3ff8 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -26,9 +26,9 @@
 #include <input/Input.h>
 #include <utils/Looper.h>
 #include <utils/TypeHelpers.h>
-#include <ScopedLocalRef.h>
+#include <nativehelper/ScopedLocalRef.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_os_MessageQueue.h"
 #include "android_view_KeyEvent.h"
 #include "android_view_MotionEvent.h"
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index e5519a7..586b26e 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -20,7 +20,7 @@
 #include <input/Input.h>
 #include <binder/Parcel.h>
 
-#include <nativehelper/jni.h>
+#include <jni.h>
 #include <nativehelper/JNIHelp.h>
 
 #include "android_os_Parcel.h"
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index 216e6f6..8a6e745 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -16,13 +16,13 @@
 
 #define LOG_TAG "KeyEvent-JNI"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <utils/Log.h>
 #include <input/Input.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "android_view_KeyEvent.h"
 
 #include "core_jni_helpers.h"
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 0245d38..f11b0dc 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -16,14 +16,14 @@
 
 #define LOG_TAG "MotionEvent-JNI"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <SkMatrix.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <utils/Log.h>
 #include <input/Input.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "android_os_Parcel.h"
 #include "android_view_MotionEvent.h"
 #include "android_util_Binder.h"
@@ -345,8 +345,10 @@
         return 0;
     }
 
-    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    if (!event) {
+    MotionEvent* event;
+    if (nativePtr) {
+        event = reinterpret_cast<MotionEvent*>(nativePtr);
+    } else {
         event = new MotionEvent();
     }
 
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
index 4150636..4f79790 100644
--- a/core/jni/android_view_PointerIcon.cpp
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "PointerIcon-JNI"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include "android_view_PointerIcon.h"
 
@@ -24,7 +24,7 @@
 #include <android_runtime/Log.h>
 #include <utils/Log.h>
 #include <android/graphics/GraphicsJNI.h>
-#include "ScopedLocalRef.h"
+#include <nativehelper/ScopedLocalRef.h>
 
 #include "core_jni_helpers.h"
 
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index a0c62c3..cb96f61 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -19,7 +19,7 @@
 #include <stdio.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_os_Parcel.h"
 #include "android/graphics/GraphicsJNI.h"
 
@@ -45,7 +45,7 @@
 #include <utils/misc.h>
 #include <utils/Log.h>
 
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 #include <AnimationContext.h>
 #include <FrameInfo.h>
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 73b3f52..d18fc2d 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -23,8 +23,9 @@
 #include "android/graphics/Region.h"
 #include "core_jni_helpers.h"
 
-#include <JNIHelp.h>
-#include <ScopedUtfChars.h>
+#include <android-base/chrono_utils.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/android_view_SurfaceSession.h>
 #include <gui/Surface.h>
@@ -495,8 +496,9 @@
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return;
 
-    ALOGD_IF_SLOW(100, "Excessive delay in setPowerMode()");
+    android::base::Timer t;
     SurfaceComposerClient::setDisplayPowerMode(token, mode);
+    if (t.duration() > 100ms) ALOGD("Excessive delay in setPowerMode()");
 }
 
 static jboolean nativeClearContentFrameStats(JNIEnv* env, jclass clazz, jlong nativeObject) {
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index dad6958..99a445e 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "SurfaceSession"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_view_SurfaceSession.h>
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 9c0a65a..094bf9a 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -23,7 +23,7 @@
 #include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 #include <GraphicsJNI.h>
-#include <ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
diff --git a/core/jni/android_view_VelocityTracker.cpp b/core/jni/android_view_VelocityTracker.cpp
index e1f2241..153789c 100644
--- a/core/jni/android_view_VelocityTracker.cpp
+++ b/core/jni/android_view_VelocityTracker.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VelocityTracker-JNI"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
@@ -24,7 +24,7 @@
 #include <input/VelocityTracker.h>
 #include "android_view_MotionEvent.h"
 
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 #include "core_jni_helpers.h"
 
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index f8f9efe..fce5dd5 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -19,7 +19,7 @@
 
 #include "core_jni_helpers.h"
 
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <androidfw/ZipFileRO.h>
 #include <androidfw/ZipUtils.h>
 #include <utils/Log.h>
diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
index 4a2b881..0cb6935 100644
--- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
@@ -24,9 +24,9 @@
 #include <core_jni_helpers.h>
 #include <jni.h>
 
-#include <ScopedUtfChars.h>
-#include <ScopedLocalRef.h>
-#include <ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 #include <utils/Log.h>
 #include <utils/misc.h>
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index cb53106..dba8194 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -54,10 +54,10 @@
 #include <processgroup/processgroup.h>
 
 #include "core_jni_helpers.h"
-#include "JNIHelp.h"
-#include "ScopedLocalRef.h"
-#include "ScopedPrimitiveArray.h"
-#include "ScopedUtfChars.h"
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "fd_utils.h"
 
 #include "nativebridge/native_bridge.h"
@@ -108,13 +108,9 @@
      // changes its locking strategy or its use of syscalls within the
      // lazy-init critical section, its use here may become unsafe.
     if (WIFEXITED(status)) {
-      if (WEXITSTATUS(status)) {
-        ALOGI("Process %d exited cleanly (%d)", pid, WEXITSTATUS(status));
-      }
+      ALOGI("Process %d exited cleanly (%d)", pid, WEXITSTATUS(status));
     } else if (WIFSIGNALED(status)) {
-      if (WTERMSIG(status) != SIGKILL) {
-        ALOGI("Process %d exited due to signal (%d)", pid, WTERMSIG(status));
-      }
+      ALOGI("Process %d exited due to signal (%d)", pid, WTERMSIG(status));
       if (WCOREDUMP(status)) {
         ALOGI("Process %d dumped core.", pid);
       }
diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
index 3d63b01..1873118 100644
--- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
@@ -15,7 +15,7 @@
 ** limitations under the License.
 */
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/android_graphics_SurfaceTexture.h>
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
index 3e74d1c..fe012d7 100644
--- a/core/jni/com_google_android_gles_jni_GLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -22,7 +22,7 @@
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 
diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h
index 3f169c3..1325b0c 100644
--- a/core/jni/core_jni_helpers.h
+++ b/core/jni/core_jni_helpers.h
@@ -17,7 +17,7 @@
 #ifndef CORE_JNI_HELPERS
 #define CORE_JNI_HELPERS
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 
 namespace android {
diff --git a/core/jni/include/android_runtime/AndroidRuntime.h b/core/jni/include/android_runtime/AndroidRuntime.h
index c2189d4..3ec8b1f 100644
--- a/core/jni/include/android_runtime/AndroidRuntime.h
+++ b/core/jni/include/android_runtime/AndroidRuntime.h
@@ -26,7 +26,7 @@
 #include <utils/Vector.h>
 #include <utils/threads.h>
 #include <pthread.h>
-#include <nativehelper/jni.h>
+#include <jni.h>
 
 
 namespace android {
diff --git a/core/jni/include/android_runtime/android_view_InputQueue.h b/core/jni/include/android_runtime/android_view_InputQueue.h
index ed37b0a..ac8da58 100644
--- a/core/jni/include/android_runtime/android_view_InputQueue.h
+++ b/core/jni/include/android_runtime/android_view_InputQueue.h
@@ -22,7 +22,7 @@
 #include <utils/TypeHelpers.h>
 #include <utils/Vector.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 /*
  * Declare a concrete type for the NDK's AInputQueue forward declaration
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 914099f..387eb1d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -142,6 +142,7 @@
     <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
     <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
     <protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" />
+    <protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.devicepicker.action.LAUNCH" />
     <protected-broadcast android:name="android.bluetooth.devicepicker.action.DEVICE_SELECTED" />
     <protected-broadcast
@@ -272,6 +273,7 @@
     <protected-broadcast android:name="com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG" />
     <protected-broadcast android:name="com.android.nfc.handover.action.ALLOW_CONNECT" />
     <protected-broadcast android:name="com.android.nfc.handover.action.DENY_CONNECT" />
+    <protected-broadcast android:name="com.android.nfc.handover.action.TIMEOUT_CONNECT" />
     <protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_ON_DETECTED" />
     <protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED" />
     <protected-broadcast android:name="com.android.nfc_extras.action.AID_SELECTED" />
@@ -1603,6 +1605,10 @@
     <permission android:name="android.permission.RECEIVE_STK_COMMANDS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to send EMBMS download intents to apps-->
+    <permission android:name="android.permission.SEND_EMBMS_INTENTS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Must be required by an ImsService to ensure that only the
          system can bind to it.
          <p>Protection level: signature|privileged
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 066a538..d51a304 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3462,6 +3462,13 @@
         <attr name="name" />
     </declare-styleable>
 
+    <!-- Specify one or more <code>t3tPmm-filter</code> elements inside a
+         <code>host-nfcf-service</code> element to specify a LF_T3T_PMM -->
+    <declare-styleable name="T3tPmmFilter">
+        <attr name="name" />
+
+    </declare-styleable>
+
     <declare-styleable name="ActionMenuItemView">
         <attr name="minWidth" />
     </declare-styleable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7dbd44a..af78669 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -281,6 +281,18 @@
          Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
     <integer translatable="false" name="config_networkAvoidBadWifi">1</integer>
 
+    <!-- If the hardware supports specially marking packets that caused a wakeup of the
+         main CPU, set this value to the mark used. -->
+    <integer name="config_networkWakeupPacketMark">0</integer>
+
+    <!-- Mask to use when checking skb mark defined in config_networkWakeupPacketMark above. -->
+    <integer name="config_networkWakeupPacketMask">0</integer>
+
+    <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
+         Those frames are identified by the field Eth-type having values
+         less than 0x600 -->
+    <bool translatable="false" name="config_apfDrop802_3Frames">true</bool>
+
     <!-- Default value for ConnectivityManager.getMultipathPreference() on metered networks. Actual
          device behaviour is controlled by Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE.
          This is the default value of that setting. -->
@@ -393,12 +405,21 @@
     <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
     <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
     <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
+    <!-- This list is also modified by code within the framework, including:
+
+             - TYPE_ETHERNET (9) is prepended to this list, and
+             - the output of TelephonyManager.getTetherApnRequired()
+               determines whether both TYPE_MOBILE (0) and TYPE_HIPRI (5)
+               or TYPE_MOBILE_DUN (4) are appended (if not already present).
+
+         For other changes applied to this list, now and in the future, see
+         com.android.server.connectivity.tethering.TetheringConfiguration.
+      -->
     <integer-array translatable="false" name="config_tether_upstream_types">
         <item>0</item>
         <item>1</item>
         <item>5</item>
         <item>7</item>
-        <item>9</item>
     </integer-array>
 
     <!-- If the DUN connection for this CDMA device supports more than just DUN -->
@@ -1301,7 +1322,7 @@
          permission.
          [This is only used if config_enableUpdateableTimeZoneRules and
          config_timeZoneRulesUpdateTrackingEnabled are true.] -->
-    <string name="config_timeZoneRulesUpdaterPackage" translateable="false"></string>
+    <string name="config_timeZoneRulesUpdaterPackage" translateable="false">com.android.timezone.updater</string>
 
     <!-- The package of the time zone rules data application. Expected to be configured
          by OEMs to reference their own priv-app APK package.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cd6d280..c53093f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1788,6 +1788,9 @@
   <java-symbol type="integer" name="config_networkNotifySwitchType" />
   <java-symbol type="array" name="config_networkNotifySwitches" />
   <java-symbol type="integer" name="config_networkAvoidBadWifi" />
+  <java-symbol type="integer" name="config_networkWakeupPacketMark" />
+  <java-symbol type="integer" name="config_networkWakeupPacketMask" />
+  <java-symbol type="bool" name="config_apfDrop802_3Frames" />
   <java-symbol type="integer" name="config_networkMeteredMultipathPreference" />
   <java-symbol type="integer" name="config_notificationsBatteryFullARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryLedOff" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 644638d..7e9f561 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -61,21 +61,27 @@
     <shortcode country="bh" pattern="\\d{1,5}" free="81181" />
 
     <!-- Brazil: 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d" />
+    <shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d|876|5500|9963" />
 
     <!-- Belarus: 4 digits -->
     <shortcode country="by" pattern="\\d{4}" premium="3336|4161|444[4689]|501[34]|7781" />
 
     <!-- Canada: 5-6 digits -->
-    <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" />
+    <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" />
 
     <!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf -->
     <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765" />
 
+    <!-- Chile: 4-5 digits (not confirmed), known premium codes listed -->
+    <shortcode country="cl" pattern="\\d{4,5}" free="9963" />
+
     <!-- China: premium shortcodes start with "1066", free shortcodes start with "1065":
          http://clients.txtnation.com/entries/197192-china-premium-sms-short-code-requirements -->
     <shortcode country="cn" premium="1066.*" free="1065.*" />
 
+    <!-- Colombia: 1-6 digits (not confirmed) -->
+    <shortcode country="co" pattern="\\d{1,6}" free="890350" />
+
     <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
 
@@ -84,7 +90,7 @@
     <shortcode country="cz" premium="9\\d{6,7}" free="116\\d{3}" />
 
     <!-- Germany: 4-5 digits plus 1232xxx (premium codes from http://www.vodafone.de/infofaxe/537.pdf and http://premiumdienste.eplus.de/pdf/kodex.pdf), plus EU. To keep the premium regex from being too large, it only includes payment processors that have been used by SMS malware, with the regular pattern matching the other premium short codes. -->
-    <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}|81214|81215|47529|70296|83782" />
+    <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}|81214|81215|47529|70296|83782|3011|73240" />
 
     <!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
     <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}|4665" />
@@ -108,11 +114,14 @@
     <!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU:
          http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf,
          visual voicemail code for EE: 887 -->
-    <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}|2020|35890|61002|61202|887|83669|34664|40406|60174" />
+    <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}|2020|35890|61002|61202|887|83669|34664|40406|60174|7726|37726" />
 
     <!-- Georgia: 4 digits, known premium codes listed -->
     <shortcode country="ge" pattern="\\d{4}" premium="801[234]|888[239]" />
 
+    <!-- Ghana: 4 digits, known premium codes listed -->
+    <shortcode country="gh" pattern="\\d{4}" free="5041" />
+
     <!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece -->
     <shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}|12115" />
 
@@ -143,6 +152,9 @@
     <!-- Japan: 8083 used by SOFTBANK_DCB_2 -->
     <shortcode country="jp" free="8083" />
 
+    <!-- Kenya: 5 digits, known premium codes listed -->
+    <shortcode country="ke" pattern="\\d{5}" free="21725" />
+
     <!-- Kyrgyzstan: 4 digits, known premium codes listed -->
     <shortcode country="kg" pattern="\\d{4}" premium="415[2367]|444[69]" />
 
@@ -160,13 +172,16 @@
 
     <!-- Luxembourg: 5 digits, 6xxxx, plus EU:
          http://www.luxgsm.lu/assets/files/filepage/file_1253803400.pdf -->
-    <shortcode country="lu" premium="6\\d{4}" free="116\\d{3}|60231" />
+    <shortcode country="lu" premium="6\\d{4}" free="116\\d{3}|60231|64085" />
 
     <!-- Latvia: 4 digits, known premium codes listed, plus EU -->
     <shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}|1399" />
 
+    <!-- Macedonia: 1-6 digits (not confirmed), known premium codes listed -->
+    <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
+
     <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
-    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052" />
+    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052|9963" />
 
     <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
     <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288" />
@@ -180,6 +195,9 @@
     <!-- New Zealand: 3-4 digits, known premium codes listed -->
     <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="3067|3068|4053" />
 
+    <!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->
+    <shortcode country="pe" pattern="\\d{4,5}" free="9963" />
+
     <!-- Philippines -->
     <shortcode country="ph" free="2147|5495|5496" />
 
@@ -196,11 +214,14 @@
     <!-- Qatar: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="qa" pattern="\\d{1,5}" free="92451" />
 
+    <!-- Reunion (French Territory): 1-5 digits (not confirmed) -->
+    <shortcode country="re" pattern="\\d{1,5}" free="38600,36300,36303,959" />
+
     <!-- Romania: 4 digits, plus EU: http://www.simplus.ro/en/resources/glossary-of-terms/ -->
     <shortcode country="ro" pattern="\\d{4}" premium="12(?:63|66|88)|13(?:14|80)" free="116\\d{3}|3654|8360" />
 
     <!-- Russia: 4 digits, known premium codes listed: http://smscoin.net/info/pricing-russia/ -->
-    <shortcode country="ru" pattern="\\d{4}" premium="1(?:1[56]1|899)|2(?:09[57]|322|47[46]|880|990)|3[589]33|4161|44(?:4[3-9]|81)|77(?:33|81)|8424" />
+    <shortcode country="ru" pattern="\\d{4}" premium="1(?:1[56]1|899)|2(?:09[57]|322|47[46]|880|990)|3[589]33|4161|44(?:4[3-9]|81)|77(?:33|81)|8424" free="6954|8501" standard="2037|2044"/>
 
     <!-- Saudi Arabia -->
     <shortcode country="sa" free="8145" />
@@ -232,9 +253,12 @@
 
     <!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm),
          visual voicemail code for T-Mobile: 122 -->
-    <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245" />
+    <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245" />
 
     <!-- Vietnam: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="vn" pattern="\\d{1,5}" free="5001|9055" />
 
+    <!-- Mayotte (French Territory): 1-5 digits (not confirmed) -->
+    <shortcode country="yt" pattern="\\d{1,5}" free="38600,36300,36303,959" />
+
 </shortcodes>
diff --git a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
index 9bbcd3d..70a0877 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
@@ -27,7 +27,6 @@
 /**
  * Tests for {@link DistroFormatVersion}.
  */
-// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
 public class DistroFormatVersionTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
index 2fbc9a1..eecae46 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
@@ -27,7 +27,6 @@
 /**
  * Tests for {@link DistroRulesVersion}.
  */
-// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
 public class DistroRulesVersionTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
index 7f4819b..99abe24 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -29,7 +29,6 @@
 /**
  * Tests for {@link RulesState}.
  */
-// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
 public class RulesStateTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
index e7a839c..91f8ebc 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
@@ -33,7 +33,6 @@
 /**
  * Tests for {@link RulesUpdaterContract}.
  */
-// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
 public class RulesUpdaterContractTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/database/DatabasePerformanceTests.java b/core/tests/coretests/src/android/database/DatabasePerformanceTests.java
deleted file mode 100644
index d0e739b..0000000
--- a/core/tests/coretests/src/android/database/DatabasePerformanceTests.java
+++ /dev/null
@@ -1,1354 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database;
-
-import junit.framework.Assert;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.provider.Contacts;
-import android.provider.Contacts.People;
-import android.test.PerformanceTestCase;
-import android.test.TestCase;
-
-import java.io.File;
-import java.util.Random;
-
-/**
- * Database Performance Tests
- * 
- */
-
-@SuppressWarnings("deprecation")
-public class DatabasePerformanceTests {
-
-    public static String[] children() {
-        return new String[] {
-            ContactReadingTest1.class.getName(),
-            Perf1Test.class.getName(),
-            Perf2Test.class.getName(),
-            Perf3Test.class.getName(),
-            Perf4Test.class.getName(),
-            Perf5Test.class.getName(),
-            Perf6Test.class.getName(),
-            Perf7Test.class.getName(),
-            Perf8Test.class.getName(),
-            Perf9Test.class.getName(),
-            Perf10Test.class.getName(),
-            Perf11Test.class.getName(),
-            Perf12Test.class.getName(),
-            Perf13Test.class.getName(),
-            Perf14Test.class.getName(),
-            Perf15Test.class.getName(),
-            Perf16Test.class.getName(),
-            Perf17Test.class.getName(),
-            Perf18Test.class.getName(),
-            Perf19Test.class.getName(),
-            Perf20Test.class.getName(),
-            Perf21Test.class.getName(),
-            Perf22Test.class.getName(),
-            Perf23Test.class.getName(),
-            Perf24Test.class.getName(),
-            Perf25Test.class.getName(),
-            Perf26Test.class.getName(),
-            Perf27Test.class.getName(),
-            Perf28Test.class.getName(),
-            Perf29Test.class.getName(),
-            Perf30Test.class.getName(),
-            Perf31Test.class.getName(),
-            };
-    }
-       
-    public static abstract class PerformanceBase implements TestCase,
-            PerformanceTestCase {
-        protected static final int CURRENT_DATABASE_VERSION = 42;
-        protected SQLiteDatabase mDatabase;
-        protected File mDatabaseFile;
-        protected Context mContext;
-
-        public void setUp(Context c) {
-            mContext = c;
-            mDatabaseFile = new File("/tmp", "perf_database_test.db");
-            if (mDatabaseFile.exists()) {
-                mDatabaseFile.delete();
-            }
-            mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
-            Assert.assertTrue(mDatabase != null);
-            mDatabase.setVersion(CURRENT_DATABASE_VERSION);
-        }
-
-        public void tearDown() {
-            mDatabase.close();
-            mDatabaseFile.delete();
-        }
-
-        public boolean isPerformanceOnly() {
-            return true;
-        }
-
-        // These test can only be run once.
-        public int startPerformance(Intermediates intermediates) {
-            return 0;
-        }
-
-        public void run() {
-        }
-
-        public String numberName(int number) {
-            String result = "";
-
-            if (number >= 1000) {
-                result += numberName((number / 1000)) + " thousand";
-                number = (number % 1000);
-
-                if (number > 0) result += " ";
-            }
-
-            if (number >= 100) {
-                result += ONES[(number / 100)] + " hundred";
-                number = (number % 100);
-
-                if (number > 0) result += " ";
-            }
-
-            if (number >= 20) {
-                result += TENS[(number / 10)];
-                number = (number % 10);
-
-                if (number > 0) result += " ";
-            }
-
-            if (number > 0) {
-                result += ONES[number];
-            }
-
-            return result;
-        }
-    }
-
-    /**
-     * Test reading all contact data.
-     */
-    public static class ContactReadingTest1 implements TestCase, PerformanceTestCase {
-        private static final String[] PEOPLE_PROJECTION = new String[] {
-               Contacts.People._ID, // 0
-               Contacts.People.PRIMARY_PHONE_ID, // 1
-               Contacts.People.TYPE, // 2
-               Contacts.People.NUMBER, // 3
-               Contacts.People.LABEL, // 4
-               Contacts.People.NAME, // 5
-               Contacts.People.PRESENCE_STATUS, // 6
-        };
-
-        private Cursor mCursor;
-
-        public void setUp(Context c) {
-            mCursor = c.getContentResolver().query(People.CONTENT_URI, PEOPLE_PROJECTION, null,
-                    null, People.DEFAULT_SORT_ORDER);
-        }
-        
-        public void tearDown() {
-            mCursor.close();
-        }
-
-        public boolean isPerformanceOnly() {
-            return true;
-        }
-
-        public int startPerformance(Intermediates intermediates) {
-            // This test can only be run once.
-            return 0;
-        }
-
-        public void run() {
-            while (mCursor.moveToNext()) {
-                // Read out all of the data
-                mCursor.getLong(0);
-                mCursor.getLong(1);
-                mCursor.getLong(2);
-                mCursor.getString(3);
-                mCursor.getString(4);
-                mCursor.getString(5);
-                mCursor.getLong(6);
-            }
-        }
-    }
-    
-    /**
-     * Test 1000 inserts
-     */
-    
-    public static class Perf1Test extends PerformanceBase {
-        private static final int SIZE = 1000;
-
-        private String[] statements = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                statements[i] =
-                        "INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                                + numberName(r) + "')";
-            }
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.execSQL(statements[i]);
-            }
-        }
-    }
-
-    /**
-     * Test 1000 inserts into and indexed table
-     */
-    
-    public static class Perf2Test extends PerformanceBase {
-        private static final int SIZE = 1000;
-
-        private String[] statements = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                statements[i] =
-                        "INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                                + numberName(r) + "')";
-            }
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.execSQL(statements[i]);
-            }
-        }
-    }
-
-    /**
-     * 100 SELECTs without an index
-     */
-      
-    public static class Perf3Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"count(*)", "avg(b)"};
-
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int lower = i * 100;
-                int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase
-                        .query("t1", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     * 100 SELECTs on a string comparison
-     */
-    
-    public static class Perf4Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"count(*)", "avg(b)"};
-
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                where[i] = "c LIKE '" + numberName(i) + "'";
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase
-                        .query("t1", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     * 100 SELECTs with an index
-     */
-    
-    public static class Perf5Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"count(*)", "avg(b)"};
-
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int lower = i * 100;
-                int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase
-                        .query("t1", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     *  INNER JOIN without an index
-     */
-    
-    public static class Perf6Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"t1.a"};
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase
-              .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-        }
-
-        @Override
-        public void run() {
-            mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
-                    null, null, null, null);
-        }
-    }
-
-    /**
-     *  INNER JOIN without an index on one side
-     */
-    
-    public static class Perf7Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"t1.a"};
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase
-              .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-        }
-
-        @Override
-        public void run() {
-            mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
-                    null, null, null, null);
-        }
-    }
-
-    /**
-     *  INNER JOIN without an index on one side
-     */
-    
-    public static class Perf8Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"t1.a"};
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase
-              .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-        }
-
-        @Override
-        public void run() {
-            mDatabase.query("t1 INNER JOIN t2 ON t1.c = t2.c", COLUMNS, null,
-                    null, null, null, null);
-        }
-    }
-
-    /**
-     *  100 SELECTs with subqueries. Subquery is using an index
-     */
-    
-    public static class Perf9Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"t1.a"};
-
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase
-              .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            mDatabase.execSQL("CREATE INDEX i2b ON t2(b)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int lower = i * 100;
-                int upper = (i + 10) * 100;
-                where[i] =
-                        "t1.b IN (SELECT t2.b FROM t2 WHERE t2.b >= " + lower
-                                + " AND t2.b < " + upper + ")";
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase
-                        .query("t1", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     *  100 SELECTs on string comparison with Index
-     */
-
-    public static class Perf10Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"count(*)", "avg(b)"};
-
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i3c ON t1(c)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                where[i] = "c LIKE '" + numberName(i) + "'";
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase
-                        .query("t1", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     *  100 SELECTs on integer 
-     */
-    
-    public static class Perf11Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"b"};
-
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t1", COLUMNS, null, null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     *  100 SELECTs on String
-     */
-
-    public static class Perf12Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"c"};
-
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t1", COLUMNS, null, null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     *  100 SELECTs on integer with index
-     */
-    
-    public static class Perf13Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"b"};
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i1b on t1(b)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t1", COLUMNS, null, null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     *  100 SELECTs on String with index
-     */
-
-    public static class Perf14Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"c"};      
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t1", COLUMNS, null, null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     *  100 SELECTs on String with starts with
-     */
-
-    public static class Perf15Test extends PerformanceBase {
-        private static final int SIZE = 100;
-        private static final String[] COLUMNS = {"c"};
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                where[i] = "c LIKE '" + numberName(r).substring(0, 1) + "*'";
-
-            }
-
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase
-                        .query("t1", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-
-    /**
-     *  1000  Deletes on an indexed table
-     */
-    
-    public static class Perf16Test extends PerformanceBase {
-        private static final int SIZE = 1000;
-        private static final String[] COLUMNS = {"c"};
-        
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i3c ON t1(c)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.delete("t1", null, null);
-            }
-        }
-    }
-
-    /**
-     *  1000  Deletes
-     */
-    
-    public static class Perf17Test extends PerformanceBase {
-        private static final int SIZE = 1000;
-        private static final String[] COLUMNS = {"c"};       
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.delete("t1", null, null);
-            }
-        }
-    }
-
-    /**
-     *  1000 DELETE's without an index with where clause 
-     */
-    
-    public static class Perf18Test extends PerformanceBase {
-        private static final int SIZE = 1000;
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int lower = i * 100;
-                int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.delete("t1", where[i], null);
-            }
-        }
-    }
-
-    /**
-     *  1000 DELETE's with an index with where clause 
-     */
-    
-    public static class Perf19Test extends PerformanceBase {
-        private static final int SIZE = 1000;
-        private String[] where = new String[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int lower = i * 100;
-                int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.delete("t1", where[i], null);
-            }
-        }
-    }
-
-    /**
-     *  1000 update's with an index with where clause 
-     */
-    
-    public static class Perf20Test extends PerformanceBase {
-        private static final int SIZE = 1000;
-        private String[] where = new String[SIZE];
-        ContentValues[] mValues = new ContentValues[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-
-                int lower = i * 100;
-                int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
-                ContentValues b = new ContentValues(1);
-                b.put("b", upper);
-                mValues[i] = b;
-               
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.update("t1", mValues[i], where[i], null);
-            }
-        }
-    }
-
-    /**
-     *  1000 update's without an index with where clause 
-     */
-    
-    public static class Perf21Test extends PerformanceBase {
-        private static final int SIZE = 1000;       
-        private String[] where = new String[SIZE];
-        ContentValues[] mValues = new ContentValues[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-           
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-
-                int lower = i * 100;
-                int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
-                ContentValues b = new ContentValues(1);
-                b.put("b", upper);
-                mValues[i] = b;
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.update("t1", mValues[i], where[i], null);
-            }
-        }
-    }
-    
-    /**
-     *  10000 inserts for an integer 
-     */
-    
-    public static class Perf22Test extends PerformanceBase {
-        private static final int SIZE = 10000;
-        ContentValues[] mValues = new ContentValues[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER)");
-           
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                ContentValues b = new ContentValues(1);
-                b.put("a", r);
-                mValues[i] = b;
-            }
-        }        
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.insert("t1", null, mValues[i]);
-            }
-        }
-    }
-    
-    /**
-     *  10000 inserts for an integer -indexed table
-     */
-    
-    public static class Perf23Test extends PerformanceBase {
-        private static final int SIZE = 10000;
-        ContentValues[] mValues = new ContentValues[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a INTEGER)");
-            mDatabase.execSQL("CREATE INDEX i1a ON t1(a)");
-           
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                ContentValues b = new ContentValues(1);
-                b.put("a", r);
-                mValues[i] = b;
-            }
-        }        
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.insert("t1", null, mValues[i]);
-            }
-        }
-    }
-    
-    /**
-     *  10000 inserts for a String 
-     */
-    
-    public static class Perf24Test extends PerformanceBase {
-        private static final int SIZE = 10000;
-        ContentValues[] mValues = new ContentValues[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a VARCHAR(100))");
-           
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                ContentValues b = new ContentValues(1);
-                b.put("a", numberName(r));
-                mValues[i] = b;
-            }
-        }        
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.insert("t1", null, mValues[i]);
-            }
-        }
-    }
-    
-    /**
-     *  10000 inserts for a String - indexed table 
-     */
-    
-    public static class Perf25Test extends PerformanceBase {
-        private static final int SIZE = 10000;       
-        ContentValues[] mValues = new ContentValues[SIZE];
-
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t1(a VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i1a ON t1(a)");
-                       
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                ContentValues b = new ContentValues(1);
-                b.put("a", numberName(r));
-                mValues[i] = b; 
-            }
-        }
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.insert("t1", null, mValues[i]);
-            }
-        }
-    }
-    
-    
-    /**
-     *  10000 selects for a String -starts with
-     */
-    
-    public static class Perf26Test extends PerformanceBase {
-        private static final int SIZE = 10000;
-        private static final String[] COLUMNS = {"t3.a"};
-        private String[] where = new String[SIZE];
-        
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t3(a VARCHAR(100))");
-                                  
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t3 VALUES('"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'";
-
-            }
-        }        
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-    
-    /**
-     *  10000 selects for a String - indexed table -starts with
-     */
-    
-    public static class Perf27Test extends PerformanceBase {
-        private static final int SIZE = 10000;
-        private static final String[] COLUMNS = {"t3.a"};
-        private String[] where = new String[SIZE];
-        
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t3(a VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i3a ON t3(a)");
-                       
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t3 VALUES('"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'";
-
-            }                              
-           }        
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-    
-    /**
-     *  10000 selects for an integer -
-     */
-    
-    public static class Perf28Test extends PerformanceBase {
-        private static final int SIZE = 10000;
-        private static final String[] COLUMNS = {"t4.a"};
-        private String[] where = new String[SIZE];
-        
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t4(a INTEGER)");
-           
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")");
-                int lower = i * 100;
-                int upper = (i + 10) * 100;
-                where[i] = "a >= " + lower + " AND a < " + upper;
-            }
-           }        
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t4", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-    
-    /**
-     *  10000 selects for an integer -indexed table
-     */
-    
-    public static class Perf29Test extends PerformanceBase {
-        private static final int SIZE = 10000;
-        private static final String[] COLUMNS = {"t4.a"};
-        private String[] where = new String[SIZE];
-       
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t4(a INTEGER)");
-           mDatabase.execSQL("CREATE INDEX i4a ON t4(a)");
-           
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")");
-                
-                int lower = i * 100;
-                int upper = (i + 10) * 100;
-                where[i] = "a >= " + lower + " AND a < " + upper;
-            }
-           
-           }        
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t4", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-    
-    
-    /**
-     *  10000 selects for a String - contains 'e'
-     */
-    
-    public static class Perf30Test extends PerformanceBase {
-        private static final int SIZE = 10000;
-        private static final String[] COLUMNS = {"t3.a"};
-        private String[] where = new String[SIZE];
-        
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t3(a VARCHAR(100))");
-            
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t3 VALUES('"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                 where[i] = "a LIKE '*e*'";
-
-            }                              
-           }        
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-    
-    /**
-     *  10000 selects for a String - contains 'e'-indexed table
-     */
-    
-    public static class Perf31Test extends PerformanceBase {
-        private static final int SIZE = 10000;
-        private static final String[] COLUMNS = {"t3.a"};
-        private String[] where = new String[SIZE];
-        
-        @Override
-        public void setUp(Context c) {
-            super.setUp(c);
-            Random random = new Random(42);
-
-            mDatabase
-              .execSQL("CREATE TABLE t3(a VARCHAR(100))");
-            mDatabase.execSQL("CREATE INDEX i3a ON t3(a)");
-            
-            for (int i = 0; i < SIZE; i++) {
-                int r = random.nextInt(100000);
-                mDatabase.execSQL("INSERT INTO t3 VALUES('"
-                        + numberName(r) + "')");
-            }
-
-            for (int i = 0; i < SIZE; i++) {
-                where[i] = "a LIKE '*e*'";
-
-            }                              
-            
-           }        
-
-        @Override
-        public void run() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
-            }
-        }
-    }
-    
-    public static final String[] ONES =
-            {"zero", "one", "two", "three", "four", "five", "six", "seven",
-                "eight", "nine", "ten", "eleven", "twelve", "thirteen",
-                "fourteen", "fifteen", "sixteen", "seventeen", "eighteen",
-                "nineteen"};
-
-    public static final String[] TENS =
-            {"", "ten", "twenty", "thirty", "forty", "fifty", "sixty",
-                "seventy", "eighty", "ninety"};
-}
diff --git a/core/tests/coretests/src/android/net/SSLSessionCacheTest.java b/core/tests/coretests/src/android/net/SSLSessionCacheTest.java
index ec130e0..11d066b 100644
--- a/core/tests/coretests/src/android/net/SSLSessionCacheTest.java
+++ b/core/tests/coretests/src/android/net/SSLSessionCacheTest.java
@@ -44,12 +44,9 @@
 
         try {
             SSLSessionCache.install(new SSLSessionCache(mock), ctx);
-            clientCtx.getSession("www.foogle.com", 443);
-            Mockito.verify(mock).getSessionData(Mockito.anyString(), Mockito.anyInt());
         } finally {
             // Restore cacheless behaviour.
             SSLSessionCache.install(null, ctx);
-            clientCtx.getSession("www.foogle.com", 443);
             Mockito.verifyNoMoreInteractions(mock);
         }
     }
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
index e69a2cc..853d78b 100644
--- a/core/tests/utiltests/Android.mk
+++ b/core/tests/utiltests/Android.mk
@@ -18,7 +18,6 @@
     android-support-test \
     frameworks-base-testutils \
     mockito-target \
-    legacy-android-tests
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp
index 63fe8ac..52ea8e3 100644
--- a/drm/jni/android_drm_DrmManagerClient.cpp
+++ b/drm/jni/android_drm_DrmManagerClient.cpp
@@ -19,8 +19,8 @@
 #include <utils/Log.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
-#include <ScopedLocalRef.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
 #include <android_runtime/AndroidRuntime.h>
 
 #include <drm/DrmInfo.h>
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index 8efda2a..e683999 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -48,19 +48,6 @@
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
-# Build the legacy-android-tests library
-# ======================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, tests)
-LOCAL_MODULE := legacy-android-tests
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart framework junit
-LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
 ifeq ($(HOST_OS),linux)
 # Build the legacy-performance-test-hostdex library
 # =================================================
diff --git a/test-runner/src/android/test/suitebuilder/annotation/package.html b/legacy-test/src/android/test/suitebuilder/annotation/package.html
similarity index 100%
rename from test-runner/src/android/test/suitebuilder/annotation/package.html
rename to legacy-test/src/android/test/suitebuilder/annotation/package.html
diff --git a/legacy-test/src/com/android/internal/util/Predicates.java b/legacy-test/src/com/android/internal/util/Predicates.java
deleted file mode 100644
index fe1ff15..0000000
--- a/legacy-test/src/com/android/internal/util/Predicates.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import java.util.Arrays;
-
-/**
- * Predicates contains static methods for creating the standard set of
- * {@code Predicate} objects.
- *
- * @hide
- */
-public class Predicates {
-
-    private Predicates() {
-    }
-
-    /**
-     * Returns a Predicate that evaluates to true iff each of its components
-     * evaluates to true.  The components are evaluated in order, and evaluation
-     * will be "short-circuited" as soon as the answer is determined.
-     */
-    public static <T> Predicate<T> and(Predicate<? super T>... components) {
-        return Predicates.<T>and(Arrays.asList(components));
-    }
-
-    /**
-     * Returns a Predicate that evaluates to true iff each of its components
-     * evaluates to true.  The components are evaluated in order, and evaluation
-     * will be "short-circuited" as soon as the answer is determined.  Does not
-     * defensively copy the iterable passed in, so future changes to it will alter
-     * the behavior of this Predicate. If components is empty, the returned
-     * Predicate will always evaluate to true.
-     */
-    public static <T> Predicate<T> and(Iterable<? extends Predicate<? super T>> components) {
-        return new AndPredicate(components);
-    }
-
-    /**
-     * Returns a Predicate that evaluates to true iff any one of its components
-     * evaluates to true.  The components are evaluated in order, and evaluation
-     * will be "short-circuited" as soon as the answer is determined.
-     */
-    public static <T> Predicate<T> or(Predicate<? super T>... components) {
-        return Predicates.<T>or(Arrays.asList(components));
-    }
-
-    /**
-     * Returns a Predicate that evaluates to true iff any one of its components
-     * evaluates to true.  The components are evaluated in order, and evaluation
-     * will be "short-circuited" as soon as the answer is determined.  Does not
-     * defensively copy the iterable passed in, so future changes to it will alter
-     * the behavior of this Predicate. If components is empty, the returned
-     * Predicate will always evaluate to false.
-     */
-    public static <T> Predicate<T> or(Iterable<? extends Predicate<? super T>> components) {
-        return new OrPredicate(components);
-    }
-
-    /**
-     * Returns a Predicate that evaluates to true iff the given Predicate
-     * evaluates to false.
-     */
-    public static <T> Predicate<T> not(Predicate<? super T> predicate) {
-        return new NotPredicate<T>(predicate);
-    }
-
-    private static class AndPredicate<T> implements Predicate<T> {
-        private final Iterable<? extends Predicate<? super T>> components;
-
-        private AndPredicate(Iterable<? extends Predicate<? super T>> components) {
-            this.components = components;
-        }
-
-        public boolean apply(T t) {
-            for (Predicate<? super T> predicate : components) {
-                if (!predicate.apply(t)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    private static class OrPredicate<T> implements Predicate<T> {
-        private final Iterable<? extends Predicate<? super T>> components;
-
-        private OrPredicate(Iterable<? extends Predicate<? super T>> components) {
-            this.components = components;
-        }
-
-        public boolean apply(T t) {
-            for (Predicate<? super T> predicate : components) {
-                if (predicate.apply(t)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    private static class NotPredicate<T> implements Predicate<T> {
-        private final Predicate<? super T> predicate;
-
-        private NotPredicate(Predicate<? super T> predicate) {
-            this.predicate = predicate;
-        }
-
-        public boolean apply(T t) {
-            return !predicate.apply(t);
-        }
-    }
-}
diff --git a/legacy-test/tests/com/android/internal/util/PredicatesTest.java b/legacy-test/tests/com/android/internal/util/PredicatesTest.java
deleted file mode 100644
index c46ff05..0000000
--- a/legacy-test/tests/com/android/internal/util/PredicatesTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.Collections;
-
-public class PredicatesTest extends TestCase {
-
-    private static final Predicate<Object> TRUE = new Predicate<Object>() {
-        public boolean apply(Object o) {
-            return true;
-        }
-    };
-
-    private static final Predicate<Object> FALSE = new Predicate<Object>() {
-        public boolean apply(Object o) {
-            return false;
-        }
-    };
-
-    public void testAndPredicate_AllConditionsTrue() throws Exception {
-        assertTrue(Predicates.and(newArrayList(TRUE)).apply(null));
-        assertTrue(Predicates.and(newArrayList(TRUE, TRUE)).apply(null));
-    }
-
-    public void testAndPredicate_AtLeastOneConditionIsFalse() throws Exception {
-        assertFalse(Predicates.and(newArrayList(FALSE, TRUE, TRUE)).apply(null));
-        assertFalse(Predicates.and(newArrayList(TRUE, FALSE, TRUE)).apply(null));
-        assertFalse(Predicates.and(newArrayList(TRUE, TRUE, FALSE)).apply(null));
-    }
-
-    public void testOrPredicate_AllConditionsTrue() throws Exception {
-        assertTrue(Predicates.or(newArrayList(TRUE, TRUE, TRUE)).apply(null));
-    }
-
-    public void testOrPredicate_AllConditionsFalse() throws Exception {
-        assertFalse(Predicates.or(newArrayList(FALSE, FALSE, FALSE)).apply(null));
-    }
-
-    public void testOrPredicate_AtLeastOneConditionIsTrue() throws Exception {
-        assertTrue(Predicates.or(newArrayList(TRUE, FALSE, FALSE)).apply(null));
-        assertTrue(Predicates.or(newArrayList(FALSE, TRUE, FALSE)).apply(null));
-        assertTrue(Predicates.or(newArrayList(FALSE, FALSE, TRUE)).apply(null));
-    }
-
-    public void testNotPredicate() throws Exception {
-        assertTrue(Predicates.not(FALSE).apply(null));
-        assertFalse(Predicates.not(TRUE).apply(null));
-    }
-
-    private static <E> ArrayList<E> newArrayList(E... elements) {
-        ArrayList<E> list = new ArrayList<E>();
-        Collections.addAll(list, elements);
-        return list;
-    }
-
-}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 5d12d95..1198962 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -64,7 +64,16 @@
             shared: {
                 enabled: false,
             },
-            shared_libs: ["libz-host"],
+            static_libs: [
+                "libziparchive",
+                "libbase",
+                "liblog",
+                "libcutils",
+                "libutils",
+            ],
+            shared_libs: [
+                "libz-host",
+            ],
         },
         windows: {
             enabled: true,
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 38d2a58..b3491d0 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -3203,13 +3203,14 @@
         clearBagCache();
         const size_t numTypes = types.size();
         for (size_t i = 0; i < numTypes; i++) {
-            const TypeList& typeList = types[i];
+            TypeList& typeList = types.editItemAt(i);
             const size_t numInnerTypes = typeList.size();
             for (size_t j = 0; j < numInnerTypes; j++) {
                 if (typeList[j]->package->owner == owner) {
                     delete typeList[j];
                 }
             }
+            typeList.clear();
         }
 
         const size_t N = packages.size();
diff --git a/libs/hwui/ShadowTessellator.h b/libs/hwui/ShadowTessellator.h
index 5f4c9c5..2eaf187 100644
--- a/libs/hwui/ShadowTessellator.h
+++ b/libs/hwui/ShadowTessellator.h
@@ -80,8 +80,6 @@
 
     static Vector2 centroid2d(const Vector2* poly, int polyLength);
 
-    static bool isClockwise(const Vector2* polygon, int len);
-
     static Vector2 calculateNormal(const Vector2& p1, const Vector2& p2);
 
     static int getExtraVertexNumber(const Vector2& vector1, const Vector2& vector2,
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index cc96a13..7b0a1bc 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -302,21 +302,6 @@
 }
 
 /**
- * Make the polygon turn clockwise.
- *
- * @param polygon the polygon as a Vector2 array.
- * @param len the number of points of the polygon
- */
-void SpotShadow::makeClockwise(Vector2* polygon, int len) {
-    if (polygon == nullptr  || len == 0) {
-        return;
-    }
-    if (!ShadowTessellator::isClockwise(polygon, len)) {
-        reverse(polygon, len);
-    }
-}
-
-/**
  * Reverse the polygon
  *
  * @param polygon the polygon as a Vector2 array
diff --git a/libs/hwui/SpotShadow.h b/libs/hwui/SpotShadow.h
index 62a7e5d..6108bb6 100644
--- a/libs/hwui/SpotShadow.h
+++ b/libs/hwui/SpotShadow.h
@@ -54,7 +54,6 @@
     static void quicksortX(Vector2* points, int low, int high);
 
     static bool testPointInsidePolygon(const Vector2 testPoint, const Vector2* poly, int len);
-    static void makeClockwise(Vector2* polygon, int len);
     static void reverse(Vector2* polygon, int len);
 
     static void generateTriangleStrip(bool isCasterOpaque, float shadowStrengthScale,
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index d9e8116..4ea440d 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -416,7 +416,9 @@
             mProcessor = new TessellationProcessor(Caches::getInstance());
         }
         mProcessor->add(task);
-        mCache.put(entry, buffer);
+        bool inserted = mCache.put(entry, buffer);
+        // Note to the static analyzer that this insert should always succeed.
+        LOG_ALWAYS_FATAL_IF(!inserted, "buffers shouldn't spontaneously appear in the cache");
     }
     return buffer;
 }
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 55af33e..7b3199c 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -220,7 +220,7 @@
     /**
      * Draws a VectorDrawable onto the canvas.
      */
-    virtual void drawVectorDrawable(VectorDrawableRoot* tree);
+    virtual void drawVectorDrawable(VectorDrawableRoot* tree) = 0;
 
     /**
      * Converts utf16 text to glyphs, calculating position and boundary,
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index c655b7c..fcc975c 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -31,7 +31,7 @@
 #include <android_runtime/android_view_Surface.h>
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 #include <stdint.h>
 #include <inttypes.h>
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index 56df32f..1cfb3cf 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -26,7 +26,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_view_Surface.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 #include <stdint.h>
 #include <inttypes.h>
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 810996e..79d6c53 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -25,7 +25,7 @@
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <cutils/compiler.h>
 
diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp
index de9bf1f..8de11ca 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -26,7 +26,7 @@
 
 #include "android_runtime/AndroidRuntime.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_media_Utils.h"
 
 using namespace android;
diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp
index 2adbfee..a2abef5 100644
--- a/media/jni/android_media_MediaCrypto.cpp
+++ b/media/jni/android_media_MediaCrypto.cpp
@@ -22,7 +22,7 @@
 
 #include "android_runtime/AndroidRuntime.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <binder/IServiceManager.h>
 #include <cutils/properties.h>
diff --git a/media/jni/android_media_MediaDataSource.cpp b/media/jni/android_media_MediaDataSource.cpp
index 2ab7e39..8c38d88 100644
--- a/media/jni/android_media_MediaDataSource.cpp
+++ b/media/jni/android_media_MediaDataSource.cpp
@@ -23,7 +23,7 @@
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <binder/MemoryDealer.h>
 #include <drm/drm_framework_common.h>
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 7a98c95..31e227b 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -24,7 +24,7 @@
 #include "android_runtime/Log.h"
 #include "android_os_Parcel.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <binder/IServiceManager.h>
 #include <binder/Parcel.h>
diff --git a/media/jni/android_media_MediaHTTPConnection.cpp b/media/jni/android_media_MediaHTTPConnection.cpp
index fa0b43f..365e045 100644
--- a/media/jni/android_media_MediaHTTPConnection.cpp
+++ b/media/jni/android_media_MediaHTTPConnection.cpp
@@ -27,7 +27,7 @@
 
 #include "android_runtime/AndroidRuntime.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 namespace android {
 
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index f4e940d..4f1a145 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -28,7 +28,7 @@
 #include <private/media/VideoFrame.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_media_MediaDataSource.h"
 #include "android_media_Utils.h"
diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp
index 216624e..8b67346 100644
--- a/media/jni/android_media_MediaMuxer.cpp
+++ b/media/jni/android_media_MediaMuxer.cpp
@@ -21,7 +21,7 @@
 #include "android_media_Utils.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 8f14b79..8392b9e 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -30,7 +30,7 @@
 #include <fcntl.h>
 #include <utils/threads.h>
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/Log.h"
diff --git a/media/jni/android_media_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp
index 5800043..5bc8092 100644
--- a/media/jni/android_media_MediaProfiles.cpp
+++ b/media/jni/android_media_MediaProfiles.cpp
@@ -22,7 +22,7 @@
 #include <utils/threads.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include <media/MediaProfiles.h>
 
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
index 0f3c61f..3b475b2 100644
--- a/media/jni/android_media_MediaScanner.cpp
+++ b/media/jni/android_media_MediaScanner.cpp
@@ -24,7 +24,7 @@
 #include <private/media/VideoFrame.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
 
diff --git a/media/jni/android_media_MediaSync.cpp b/media/jni/android_media_MediaSync.cpp
index 6de5ea9..f752008 100644
--- a/media/jni/android_media_MediaSync.cpp
+++ b/media/jni/android_media_MediaSync.cpp
@@ -26,7 +26,7 @@
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <gui/Surface.h>
 
diff --git a/media/jni/android_media_ResampleInputStream.cpp b/media/jni/android_media_ResampleInputStream.cpp
index fa75524..3d8f517c 100644
--- a/media/jni/android_media_ResampleInputStream.cpp
+++ b/media/jni/android_media_ResampleInputStream.cpp
@@ -27,7 +27,7 @@
 #include <utils/threads.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 
 
diff --git a/media/jni/android_media_SyncParams.cpp b/media/jni/android_media_SyncParams.cpp
index d9b2f1d..2d9738d 100644
--- a/media/jni/android_media_SyncParams.cpp
+++ b/media/jni/android_media_SyncParams.cpp
@@ -16,7 +16,7 @@
 
 #include "android_media_SyncParams.h"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 namespace android {
 
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index af2f2d7..821c6b2 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -23,7 +23,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <gui/CpuConsumer.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <utils/KeyedVector.h>
 #include <utils/String8.h>
 #include <SkStream.h>
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index f7f79169..cf4458a 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -39,7 +39,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedLocalRef.h>
 
 #include <assert.h>
diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp
index 1faa0c4..6b7e7bb 100644
--- a/media/jni/android_mtp_MtpDevice.cpp
+++ b/media/jni/android_mtp_MtpDevice.cpp
@@ -29,8 +29,8 @@
 #include <string>
 
 #include "jni.h"
-#include "JNIHelp.h"
-#include "ScopedPrimitiveArray.h"
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index c325f4e..e9e9309 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -25,7 +25,7 @@
 #include <utils/threads.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "private/android_filesystem_config.h"
 
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index a9b7062..17c18b7 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -20,12 +20,12 @@
 #define LOG_TAG "AudioEffects-JNI"
 
 #include <utils/Log.h>
-#include <nativehelper/jni.h>
+#include <jni.h>
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include "media/AudioEffect.h"
 
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 using namespace android;
 
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index 0645543..b7d7b03 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -20,13 +20,13 @@
 #define LOG_TAG "visualizers-JNI"
 
 #include <utils/Log.h>
-#include <nativehelper/jni.h>
+#include <jni.h>
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/threads.h>
 #include "media/Visualizer.h"
 
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 using namespace android;
 
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index ab3e340..703a015 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -20,7 +20,7 @@
 #define LOG_TAG "SoundPool-JNI"
 
 #include <utils/Log.h>
-#include <nativehelper/jni.h>
+#include <jni.h>
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include "SoundPool.h"
diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp
index 0e5e5c6..98e9a42 100644
--- a/native/android/asset_manager.cpp
+++ b/native/android/asset_manager.cpp
@@ -24,7 +24,7 @@
 #include <utils/threads.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 using namespace android;
 
diff --git a/packages/CaptivePortalLogin/OWNERS b/packages/CaptivePortalLogin/OWNERS
new file mode 100644
index 0000000..fa26997
--- /dev/null
+++ b/packages/CaptivePortalLogin/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+per-file Android.mk = build.master@android.com
+
+ek@google.com
+hugobenichi@google.com
+lorenzo@google.com
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 7480ad1..4e6faf6 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -31,11 +31,13 @@
 import android.net.Proxy;
 import android.net.Uri;
 import android.net.http.SslError;
+import android.os.Build;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.TypedValue;
+import android.util.SparseArray;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -43,6 +45,7 @@
 import android.webkit.WebChromeClient;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
+import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.widget.ProgressBar;
 import android.widget.TextView;
@@ -57,6 +60,7 @@
 import java.lang.InterruptedException;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.util.Objects;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -286,6 +290,18 @@
         return null;
     }
 
+    private static String host(URL url) {
+        if (url == null) {
+            return null;
+        }
+        return url.getHost();
+    }
+
+    private static String sanitizeURL(URL url) {
+        // In non-Debug build, only show host to avoid leaking private info.
+        return Build.IS_DEBUGGABLE ? Objects.toString(url) : host(url);
+    }
+
     private void testForCaptivePortal() {
         // TODO: reuse NetworkMonitor facilities for consistent captive portal detection.
         new Thread(new Runnable() {
@@ -339,6 +355,8 @@
                     TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1,
                     getResources().getDisplayMetrics());
         private int mPagesLoaded;
+        // the host of the page that this webview is currently loading. Can be null when undefined.
+        private String mHostname;
 
         // If we haven't finished cleaning up the history, don't allow going back.
         public boolean allowBack() {
@@ -346,8 +364,8 @@
         }
 
         @Override
-        public void onPageStarted(WebView view, String url, Bitmap favicon) {
-            if (url.contains(mBrowserBailOutToken)) {
+        public void onPageStarted(WebView view, String urlString, Bitmap favicon) {
+            if (urlString.contains(mBrowserBailOutToken)) {
                 mLaunchBrowser = true;
                 done(Result.WANTED_AS_IS);
                 return;
@@ -355,11 +373,17 @@
             // The first page load is used only to cause the WebView to
             // fetch the proxy settings.  Don't update the URL bar, and
             // don't check if the captive portal is still there.
-            if (mPagesLoaded == 0) return;
+            if (mPagesLoaded == 0) {
+                return;
+            }
+            final URL url = makeURL(urlString);
+            Log.d(TAG, "onPageStarted: " + sanitizeURL(url));
+            mHostname = host(url);
             // For internally generated pages, leave URL bar listing prior URL as this is the URL
             // the page refers to.
-            if (!url.startsWith(INTERNAL_ASSETS)) {
-                getActionBar().setSubtitle(getHeaderSubtitle(url));
+            if (!urlString.startsWith(INTERNAL_ASSETS)) {
+                String subtitle = (url != null) ? getHeaderSubtitle(url) : urlString;
+                getActionBar().setSubtitle(subtitle);
             }
             getProgressBar().setVisibility(View.VISIBLE);
             testForCaptivePortal();
@@ -378,6 +402,9 @@
                 return;
             } else if (mPagesLoaded == 2) {
                 // Prevent going back to empty first page.
+                // Fix for missing focus, see b/62449959 for details. Remove it once we get a
+                // newer version of WebView (60.x.y).
+                view.requestFocus();
                 view.clearHistory();
             }
             testForCaptivePortal();
@@ -398,15 +425,18 @@
 
         @Override
         public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-            logMetricsEvent(MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR);
-            Log.w(TAG, "SSL error (error: " + error.getPrimaryError() + " host: " +
-                    // Only show host to avoid leaking private info.
-                    Uri.parse(error.getUrl()).getHost() + " certificate: " +
-                    error.getCertificate() + "); displaying SSL warning.");
-            final String sslErrorPage = makeSslErrorPage();
-            if (VDBG) {
-                Log.d(TAG, sslErrorPage);
+            final URL url = makeURL(error.getUrl());
+            final String host = host(url);
+            Log.d(TAG, String.format("SSL error: %s, url: %s, certificate: %s",
+                    sslErrorName(error), sanitizeURL(url), error.getCertificate()));
+            if (url == null || !Objects.equals(host, mHostname)) {
+                // Ignore ssl errors for resources coming from a different hostname than the page
+                // that we are currently loading, and only cancel the request.
+                handler.cancel();
+                return;
             }
+            logMetricsEvent(MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR);
+            final String sslErrorPage = makeSslErrorPage();
             view.loadDataWithBaseURL(INTERNAL_ASSETS, sslErrorPage, "text/HTML", "UTF-8", null);
         }
 
@@ -499,19 +529,30 @@
         return getString(R.string.action_bar_title, info.getExtraInfo().replaceAll("^\"|\"$", ""));
     }
 
-    private String getHeaderSubtitle(String urlString) {
-        URL url = makeURL(urlString);
-        if (url == null) {
-            return urlString;
-        }
+    private String getHeaderSubtitle(URL url) {
+        String host = host(url);
         final String https = "https";
         if (https.equals(url.getProtocol())) {
-            return https + "://" + url.getHost();
+            return https + "://" + host;
         }
-        return url.getHost();
+        return host;
     }
 
     private void logMetricsEvent(int event) {
         MetricsLogger.action(this, event, getPackageName());
     }
+
+    private static final SparseArray<String> SSL_ERRORS = new SparseArray<>();
+    static {
+        SSL_ERRORS.put(SslError.SSL_NOTYETVALID,  "SSL_NOTYETVALID");
+        SSL_ERRORS.put(SslError.SSL_EXPIRED,      "SSL_EXPIRED");
+        SSL_ERRORS.put(SslError.SSL_IDMISMATCH,   "SSL_IDMISMATCH");
+        SSL_ERRORS.put(SslError.SSL_UNTRUSTED,    "SSL_UNTRUSTED");
+        SSL_ERRORS.put(SslError.SSL_DATE_INVALID, "SSL_DATE_INVALID");
+        SSL_ERRORS.put(SslError.SSL_INVALID,      "SSL_INVALID");
+    }
+
+    private static String sslErrorName(SslError error) {
+        return SSL_ERRORS.get(error.getPrimaryError(), "UNKNOWN");
+    }
 }
diff --git a/packages/DefaultContainerService/jni/com_android_defcontainer_MeasurementUtils.cpp b/packages/DefaultContainerService/jni/com_android_defcontainer_MeasurementUtils.cpp
index 6be4849..53cdc9d 100644
--- a/packages/DefaultContainerService/jni/com_android_defcontainer_MeasurementUtils.cpp
+++ b/packages/DefaultContainerService/jni/com_android_defcontainer_MeasurementUtils.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "DefContainer-JNI"
 
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 #include <diskusage/dirsize.h>
 #include <utils/Log.h>
diff --git a/packages/PrintSpooler/jni/com_android_printspooler_util_BitmapSerializeUtils.cpp b/packages/PrintSpooler/jni/com_android_printspooler_util_BitmapSerializeUtils.cpp
index 1ce3949..7ff9ced 100644
--- a/packages/PrintSpooler/jni/com_android_printspooler_util_BitmapSerializeUtils.cpp
+++ b/packages/PrintSpooler/jni/com_android_printspooler_util_BitmapSerializeUtils.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "BitmapSerializeUtils"
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 #include <android/bitmap.h>
 #include <android/log.h>
diff --git a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
index 990d770..846ff25 100644
--- a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
+++ b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
@@ -21,7 +21,7 @@
 #include "android_runtime/AndroidRuntime.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include "proxy_resolver_v8.h"
 
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index b4630ef..d45d167 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -29,7 +29,7 @@
 #include <androidfw/ResourceTypes.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/android_util_AssetManager.h"
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 5e5c69d..4e48afc 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -26,7 +26,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     time_zone_distro \
-    tzdata_update2 \
+    time_zone_distro_installer \
     android.hidl.base-V1.0-java-static \
     android.hardware.tetheroffload.control-V1.0-java-static \
 
@@ -41,4 +41,16 @@
  -D jack.transformations.boost-locked-region-priority.request=com.android.server.am.ActivityManagerService\#boostPriorityForLockedSection \
  -D jack.transformations.boost-locked-region-priority.reset=com.android.server.am.ActivityManagerService\#resetPriorityAfterLockedSection
 
+LOCAL_JAR_PROCESSOR := lockedregioncodeinjection
+# Use = instead of := to delay evaluation of ${in} and ${out}
+LOCAL_JAR_PROCESSOR_ARGS = \
+ --targets \
+  "Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/wm/WindowHashMap;" \
+ --pre \
+  "com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection" \
+ --post \
+  "com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection" \
+ -o ${out} \
+ -i ${in}
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 2435c27..8972802 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -215,13 +215,6 @@
     // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
     private final int mReleasePendingIntentDelayMs;
 
-    // Driver specific constants used to select packets received via
-    // WiFi that caused the phone to exit sleep state. Currently there
-    // is only one kernel implementation so we can get away with
-    // constants.
-    private static final int mWakeupPacketMark = 0x80000000;
-    private static final int mWakeupPacketMask = 0x80000000;
-
     private MockableSystemProperties mSystemProperties;
 
     private Tethering mTethering;
@@ -2410,6 +2403,10 @@
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
             mKeepaliveTracker.handleStopAllKeepalives(nai,
                     ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
+            for (String iface : nai.linkProperties.getAllInterfaceNames()) {
+                // Disable wakeup packet monitoring for each interface.
+                wakeupModifyInterface(iface, nai.networkCapabilities, false);
+            }
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
             mNetworkAgentInfos.remove(msg.replyTo);
             updateClat(null, nai.linkProperties, nai);
@@ -3911,6 +3908,9 @@
     public void setProvisioningNotificationVisible(boolean visible, int networkType,
             String action) {
         enforceConnectivityInternalPermission();
+        if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
+            return;
+        }
         final long ident = Binder.clearCallingIdentity();
         try {
             // Concatenate the range of types onto the range of NetIDs.
@@ -4529,22 +4529,35 @@
         }
     }
 
-    private void wakeupAddInterface(String iface, NetworkCapabilities caps) throws RemoteException {
+    private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
         // Marks are only available on WiFi interaces. Checking for
         // marks on unsupported interfaces is harmless.
         if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
             return;
         }
-        mNetd.getNetdService().wakeupAddInterface(
-            iface, "iface:" + iface, mWakeupPacketMark, mWakeupPacketMask);
-    }
 
-    private void wakeupDelInterface(String iface, NetworkCapabilities caps) throws RemoteException {
-        if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+        int mark = mContext.getResources().getInteger(
+            com.android.internal.R.integer.config_networkWakeupPacketMark);
+        int mask = mContext.getResources().getInteger(
+            com.android.internal.R.integer.config_networkWakeupPacketMask);
+
+        // Mask/mark of zero will not detect anything interesting.
+        // Don't install rules unless both values are nonzero.
+        if (mark == 0 || mask == 0) {
             return;
         }
-        mNetd.getNetdService().wakeupDelInterface(
-            iface, "iface:" + iface, mWakeupPacketMark, mWakeupPacketMask);
+
+        final String prefix = "iface:" + iface;
+        try {
+            if (add) {
+                mNetd.getNetdService().wakeupAddInterface(iface, prefix, mark, mask);
+            } else {
+                mNetd.getNetdService().wakeupDelInterface(iface, prefix, mark, mask);
+            }
+        } catch (Exception e) {
+            loge("Exception modifying wakeup packet monitoring: " + e);
+        }
+
     }
 
     private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId,
@@ -4559,7 +4572,7 @@
             try {
                 if (DBG) log("Adding iface " + iface + " to network " + netId);
                 mNetd.addInterfaceToNetwork(iface, netId);
-                wakeupAddInterface(iface, caps);
+                wakeupModifyInterface(iface, caps, true);
             } catch (Exception e) {
                 loge("Exception adding interface: " + e);
             }
@@ -4567,8 +4580,8 @@
         for (String iface : interfaceDiff.removed) {
             try {
                 if (DBG) log("Removing iface " + iface + " from network " + netId);
+                wakeupModifyInterface(iface, caps, false);
                 mNetd.removeInterfaceFromNetwork(iface, netId);
-                wakeupDelInterface(iface, caps);
             } catch (Exception e) {
                 loge("Exception removing interface: " + e);
             }
@@ -4689,10 +4702,12 @@
      */
     private void updateCapabilities(
             int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
-        if (nai.everConnected && !nai.networkCapabilities.equalImmutableCapabilities(
-                networkCapabilities)) {
-            Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: "
-                    + nai.networkCapabilities + " -> " + networkCapabilities);
+        // Sanity check: a NetworkAgent should not change its static capabilities or parameters.
+        if (nai.everConnected) {
+            String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities);
+            if (!TextUtils.isEmpty(diff)) {
+                Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities:" + diff);
+            }
         }
 
         // Don't modify caller's NetworkCapabilities.
@@ -5060,12 +5075,12 @@
         if (!newNetwork.networkCapabilities.equalRequestableCapabilities(nc)) {
             Slog.wtf(TAG, String.format(
                     "BUG: %s changed requestable capabilities during rematch: %s -> %s",
-                    nc, newNetwork.networkCapabilities));
+                    newNetwork.name(), nc, newNetwork.networkCapabilities));
         }
         if (newNetwork.getCurrentScore() != score) {
             Slog.wtf(TAG, String.format(
                     "BUG: %s changed score during rematch: %d -> %d",
-                    score, newNetwork.getCurrentScore()));
+                   newNetwork.name(), score, newNetwork.getCurrentScore()));
         }
 
         // Second pass: process all listens.
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 6296375..cfd7242 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -264,3 +264,16 @@
 # GestureLauncherService.java
 # ---------------------------
 40100 camera_gesture_triggered (gesture_on_time|2|3), (sensor1_on_time|2|3), (sensor2_on_time|2|3), (event_extra|1|1)
+
+# ---------------------------
+# timezone/RulesManagerService.java
+# ---------------------------
+51600 timezone_trigger_check (token|3)
+51610 timezone_request_install (token|3)
+51611 timezone_install_started (token|3)
+51612 timezone_install_complete (token|3), (result|1)
+51620 timezone_request_uninstall (token|3)
+51621 timezone_uninstall_started (token|3)
+51622 timezone_uninstall_complete (token|3), (result|1)
+51630 timezone_request_nothing (token|3)
+51631 timezone_nothing_complete (token|3)
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index ec275cc..3fec6ad 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -46,6 +46,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -64,7 +65,7 @@
     private static final int[] DIRECTIONS =
             new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
 
-    private static final int NETD_FETCH_TIMEOUT = 5000; //ms
+    private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
     private static final int MAX_PORT_BIND_ATTEMPTS = 10;
     private static final InetAddress INADDR_ANY;
 
@@ -96,6 +97,24 @@
     private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords =
             new ManagedResourceArray<>();
 
+    interface IpSecServiceConfiguration {
+        INetd getNetdInstance() throws RemoteException;
+
+        static IpSecServiceConfiguration GETSRVINSTANCE =
+                new IpSecServiceConfiguration() {
+                    @Override
+                    public INetd getNetdInstance() throws RemoteException {
+                        final INetd netd = NetdService.getInstance();
+                        if (netd == null) {
+                            throw new RemoteException("Failed to Get Netd Instance");
+                        }
+                        return netd;
+                    }
+                };
+    }
+
+    private final IpSecServiceConfiguration mSrvConfig;
+
     /**
      * The ManagedResource class provides a facility to cleanly and reliably release system
      * resources. It relies on two things: an IBinder that allows ManagedResource to automatically
@@ -195,18 +214,36 @@
          * <p>Calls to this are always guarded by IpSecService#this
          */
         protected abstract void releaseResources() throws RemoteException;
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{mResourceId=")
+                    .append(mResourceId)
+                    .append(", pid=")
+                    .append(pid)
+                    .append(", uid=")
+                    .append(uid)
+                    .append(", mReferenceCount=")
+                    .append(mReferenceCount.get())
+                    .append("}")
+                    .toString();
+        }
     };
 
     /**
-     * Minimal wrapper around SparseArray that performs ownership
-     * validation on element accesses.
+     * Minimal wrapper around SparseArray that performs ownership validation on element accesses.
      */
     private class ManagedResourceArray<T extends ManagedResource> {
         SparseArray<T> mArray = new SparseArray<>();
 
         T get(int key) {
             T val = mArray.get(key);
-            val.checkOwnerOrSystemAndThrow();
+            // The value should never be null unless the resource doesn't exist
+            // (since we do not allow null resources to be added).
+            if (val != null) {
+                val.checkOwnerOrSystemAndThrow();
+            }
             return val;
         }
 
@@ -218,6 +255,11 @@
         void remove(int key) {
             mArray.remove(key);
         }
+
+        @Override
+        public String toString() {
+            return mArray.toString();
+        }
     }
 
     private final class TransformRecord extends ManagedResource {
@@ -260,7 +302,8 @@
             for (int direction : DIRECTIONS) {
                 int spi = mSpis[direction].getSpi();
                 try {
-                    getNetdInstance()
+                    mSrvConfig
+                            .getNetdInstance()
                             .ipSecDeleteSecurityAssociation(
                                     mResourceId,
                                     direction,
@@ -286,6 +329,24 @@
                 mSocket.removeReference();
             }
         }
+
+        @Override
+        public String toString() {
+            StringBuilder strBuilder = new StringBuilder();
+            strBuilder
+                    .append("{super=")
+                    .append(super.toString())
+                    .append(", mSocket=")
+                    .append(mSocket)
+                    .append(", mSpis[OUT].mResourceId=")
+                    .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId)
+                    .append(", mSpis[IN].mResourceId=")
+                    .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId)
+                    .append(", mConfig=")
+                    .append(mConfig)
+                    .append("}");
+            return strBuilder.toString();
+        }
     }
 
     private final class SpiRecord extends ManagedResource {
@@ -324,7 +385,8 @@
             }
 
             try {
-                getNetdInstance()
+                mSrvConfig
+                        .getNetdInstance()
                         .ipSecDeleteSecurityAssociation(
                                 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
             } catch (ServiceSpecificException e) {
@@ -343,11 +405,31 @@
         public void setOwnedByTransform() {
             if (mOwnedByTransform) {
                 // Programming error
-                new IllegalStateException("Cannot own an SPI twice!");
+                throw new IllegalStateException("Cannot own an SPI twice!");
             }
 
             mOwnedByTransform = true;
         }
+
+        @Override
+        public String toString() {
+            StringBuilder strBuilder = new StringBuilder();
+            strBuilder
+                    .append("{super=")
+                    .append(super.toString())
+                    .append(", mSpi=")
+                    .append(mSpi)
+                    .append(", mDirection=")
+                    .append(mDirection)
+                    .append(", mLocalAddress=")
+                    .append(mLocalAddress)
+                    .append(", mRemoteAddress=")
+                    .append(mRemoteAddress)
+                    .append(", mOwnedByTransform=")
+                    .append(mOwnedByTransform)
+                    .append("}");
+            return strBuilder.toString();
+        }
     }
 
     private final class UdpSocketRecord extends ManagedResource {
@@ -375,6 +457,19 @@
         public FileDescriptor getSocket() {
             return mSocket;
         }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{super=")
+                    .append(super.toString())
+                    .append(", mSocket=")
+                    .append(mSocket)
+                    .append(", mPort=")
+                    .append(mPort)
+                    .append("}")
+                    .toString();
+        }
     }
 
     /**
@@ -383,7 +478,7 @@
      * @param context Binder context for this service
      */
     private IpSecService(Context context) {
-        mContext = context;
+        this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
     }
 
     static IpSecService create(Context context) throws InterruptedException {
@@ -392,6 +487,13 @@
         return service;
     }
 
+    /** @hide */
+    @VisibleForTesting
+    public IpSecService(Context context, IpSecServiceConfiguration config) {
+        mContext = context;
+        mSrvConfig = config;
+    }
+
     public void systemReady() {
         if (isNetdAlive()) {
             Slog.d(TAG, "IpSecService is ready");
@@ -402,30 +504,19 @@
 
     private void connectNativeNetdService() {
         // Avoid blocking the system server to do this
-        Thread t =
-                new Thread(
-                        new Runnable() {
-                            @Override
-                            public void run() {
-                                synchronized (IpSecService.this) {
-                                    NetdService.get(NETD_FETCH_TIMEOUT);
-                                }
-                            }
-                        });
-        t.run();
-    }
-
-    INetd getNetdInstance() throws RemoteException {
-        final INetd netd = NetdService.getInstance();
-        if (netd == null) {
-            throw new RemoteException("Failed to Get Netd Instance");
-        }
-        return netd;
+        new Thread() {
+            @Override
+            public void run() {
+                synchronized (IpSecService.this) {
+                    NetdService.get(NETD_FETCH_TIMEOUT_MS);
+                }
+            }
+        }.start();
     }
 
     synchronized boolean isNetdAlive() {
         try {
-            final INetd netd = getNetdInstance();
+            final INetd netd = mSrvConfig.getNetdInstance();
             if (netd == null) {
                 return false;
             }
@@ -446,7 +537,8 @@
         String localAddress = "";
         try {
             spi =
-                    getNetdInstance()
+                    mSrvConfig
+                            .getNetdInstance()
                             .ipSecAllocateSpi(
                                     resourceId,
                                     direction,
@@ -605,38 +697,33 @@
             spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
             int spi = spis[direction].getSpi();
             try {
-                int result =
-                        getNetdInstance()
-                                .ipSecAddSecurityAssociation(
-                                        resourceId,
-                                        c.getMode(),
-                                        direction,
-                                        (c.getLocalAddress() != null)
-                                                ? c.getLocalAddress().getHostAddress()
-                                                : "",
-                                        (c.getRemoteAddress() != null)
-                                                ? c.getRemoteAddress().getHostAddress()
-                                                : "",
-                                        (c.getNetwork() != null)
-                                                ? c.getNetwork().getNetworkHandle()
-                                                : 0,
-                                        spi,
-                                        (auth != null) ? auth.getName() : "",
-                                        (auth != null) ? auth.getKey() : null,
-                                        (auth != null) ? auth.getTruncationLengthBits() : 0,
-                                        (crypt != null) ? crypt.getName() : "",
-                                        (crypt != null) ? crypt.getKey() : null,
-                                        (crypt != null) ? crypt.getTruncationLengthBits() : 0,
-                                        encapType,
-                                        encapLocalPort,
-                                        encapRemotePort);
-                if (result != spi) {
-                    // TODO: cleanup the first SA if creation of second SA fails
-                    return new IpSecTransformResponse(
-                            IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID);
-                }
+                mSrvConfig.getNetdInstance()
+                        .ipSecAddSecurityAssociation(
+                                resourceId,
+                                c.getMode(),
+                                direction,
+                                (c.getLocalAddress() != null)
+                                        ? c.getLocalAddress().getHostAddress()
+                                        : "",
+                                (c.getRemoteAddress() != null)
+                                        ? c.getRemoteAddress().getHostAddress()
+                                        : "",
+                                (c.getNetwork() != null)
+                                        ? c.getNetwork().getNetworkHandle()
+                                        : 0,
+                                spi,
+                                (auth != null) ? auth.getName() : "",
+                                (auth != null) ? auth.getKey() : null,
+                                (auth != null) ? auth.getTruncationLengthBits() : 0,
+                                (crypt != null) ? crypt.getName() : "",
+                                (crypt != null) ? crypt.getKey() : null,
+                                (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+                                encapType,
+                                encapLocalPort,
+                                encapRemotePort);
             } catch (ServiceSpecificException e) {
                 // FIXME: get the error code and throw is at an IOException from Errno Exception
+                return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
             }
         }
         // Both SAs were created successfully, time to construct a record and lock it away
@@ -680,7 +767,8 @@
         IpSecConfig c = info.getConfig();
         try {
             for (int direction : DIRECTIONS) {
-                getNetdInstance()
+                mSrvConfig
+                        .getNetdInstance()
                         .ipSecApplyTransportModeTransform(
                                 socket.getFileDescriptor(),
                                 resourceId,
@@ -708,18 +796,27 @@
     public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
             throws RemoteException {
         try {
-            getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
+            mSrvConfig
+                    .getNetdInstance()
+                    .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
         } catch (ServiceSpecificException e) {
             // FIXME: get the error code and throw is at an IOException from Errno Exception
         }
     }
 
     @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mContext.enforceCallingOrSelfPermission(DUMP, TAG);
-        // TODO: Add dump code to print out a log of all the resources being tracked
-        pw.println("IpSecService Log:");
+
+        pw.println("IpSecService dump:");
         pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
         pw.println();
+
+        pw.println("mTransformRecords:");
+        pw.println(mTransformRecords);
+        pw.println("mUdpSocketRecords:");
+        pw.println(mUdpSocketRecords);
+        pw.println("mSpiRecords:");
+        pw.println(mSpiRecords);
     }
 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 15932cc..aaec642 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.SHUTDOWN;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
@@ -53,6 +54,7 @@
 import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.INetworkManagementEventObserver;
+import android.net.ITetheringStatsProvider;
 import android.net.InterfaceConfiguration;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -220,6 +222,10 @@
 
     private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
 
+    @GuardedBy("mTetheringStatsProviders")
+    private final HashMap<ITetheringStatsProvider, String>
+            mTetheringStatsProviders = Maps.newHashMap();
+
     private final Object mQuotaLock = new Object();
 
     /** Set of interfaces with active quotas. */
@@ -319,6 +325,10 @@
 
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
+
+        synchronized (mTetheringStatsProviders) {
+            mTetheringStatsProviders.put(new NetdTetheringStatsProvider(), "netd");
+        }
     }
 
     static NetworkManagementService create(Context context, String socket)
@@ -499,6 +509,23 @@
         }
     }
 
+    @Override
+    public void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name) {
+        mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+        Preconditions.checkNotNull(provider);
+        synchronized(mTetheringStatsProviders) {
+            mTetheringStatsProviders.put(provider, name);
+        }
+    }
+
+    @Override
+    public void unregisterTetheringStatsProvider(ITetheringStatsProvider provider) {
+        mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+        synchronized(mTetheringStatsProviders) {
+            mTetheringStatsProviders.remove(provider);
+        }
+    }
+
     // Sync the state of the given chain with the native daemon.
     private void syncFirewallChainLocked(int chain, SparseIntArray uidFirewallRules, String name) {
         int size = uidFirewallRules.size();
@@ -1748,14 +1775,16 @@
         }
     }
 
-    @Override
-    public NetworkStats getNetworkStatsTethering() {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
-        try {
-            final NativeDaemonEvent[] events = mConnector.executeForList(
-                    "bandwidth", "gettetherstats");
+    private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub {
+        @Override
+        public NetworkStats getTetherStats() {
+            final NativeDaemonEvent[] events;
+            try {
+                events = mConnector.executeForList("bandwidth", "gettetherstats");
+            } catch (NativeDaemonConnectorException e) {
+                throw e.rethrowAsParcelableException();
+            }
+            final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
             for (NativeDaemonEvent event : events) {
                 if (event.getCode() != TetheringStatsListResult) continue;
 
@@ -1781,8 +1810,24 @@
                     throw new IllegalStateException("problem parsing tethering stats: " + event);
                 }
             }
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            return stats;
+        }
+    }
+
+    @Override
+    public NetworkStats getNetworkStatsTethering() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
+        synchronized (mTetheringStatsProviders) {
+            for (ITetheringStatsProvider provider: mTetheringStatsProviders.keySet()) {
+                try {
+                    stats.combineAllValues(provider.getTetherStats());
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Problem reading tethering stats from " +
+                            mTetheringStatsProviders.get(provider) + ": " + e);
+                }
+            }
         }
         return stats;
     }
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
new file mode 100644
index 0000000..e5fbdd21
--- /dev/null
+++ b/services/core/java/com/android/server/OWNERS
@@ -0,0 +1,9 @@
+per-file ConnectivityService.java=ek@google.com
+per-file ConnectivityService.java=hugobenichi@google.com
+per-file ConnectivityService.java=lorenzo@google.com
+per-file NetworkManagementService.java=ek@google.com
+per-file NetworkManagementService.java=hugobenichi@google.com
+per-file NetworkManagementService.java=lorenzo@google.com
+per-file NsdService.java=ek@google.com
+per-file NsdService.java=hugobenichi@google.com
+per-file NsdService.java=lorenzo@google.com
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index be021ea..49a7889 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -19,6 +19,7 @@
 import android.app.IActivityController;
 import android.os.Binder;
 import android.os.RemoteException;
+import com.android.internal.os.ZygoteConnectionConstants;
 import com.android.server.am.ActivityManagerService;
 
 import android.content.BroadcastReceiver;
@@ -53,6 +54,11 @@
     // Set this to true to have the watchdog record kernel thread stacks when it fires
     static final boolean RECORD_KERNEL_THREADS = true;
 
+    // Note 1: Do not lower this value below thirty seconds without tightening the invoke-with
+    //         timeout in com.android.internal.os.ZygoteConnection, or wrapped applications
+    //         can trigger the watchdog.
+    // Note 2: The debug value is already below the wait time in ZygoteConnection. Wrapped
+    //         applications may not work with a debug build. CTS will fail.
     static final long DEFAULT_TIMEOUT = DB ? 10*1000 : 60*1000;
     static final long CHECK_INTERVAL = DEFAULT_TIMEOUT / 2;
 
@@ -248,6 +254,10 @@
 
         // Initialize monitor for Binder threads.
         addMonitor(new BinderThreadMonitor());
+
+        // See the notes on DEFAULT_TIMEOUT.
+        assert DB ||
+                DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
     }
 
     public void init(Context context, ActivityManagerService activity) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c5fc038..867a7cb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16447,23 +16447,41 @@
             ArrayList<MemItem> catMems = new ArrayList<MemItem>();
 
             catMems.add(new MemItem("Native", "Native", nativePss, nativeSwapPss, -1));
-            final MemItem dalvikItem =
-                    new MemItem("Dalvik", "Dalvik", dalvikPss, dalvikSwapPss, -2);
-            if (dalvikSubitemPss.length > 0) {
-                dalvikItem.subitems = new ArrayList<MemItem>();
-                for (int j=0; j<dalvikSubitemPss.length; j++) {
-                    final String name = Debug.MemoryInfo.getOtherLabel(
-                            Debug.MemoryInfo.NUM_OTHER_STATS + j);
-                    dalvikItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j],
-                                    dalvikSubitemSwapPss[j], j));
-                }
-            }
-            catMems.add(dalvikItem);
+            final int dalvikId = -2;
+            catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, dalvikSwapPss, dalvikId));
             catMems.add(new MemItem("Unknown", "Unknown", otherPss, otherSwapPss, -3));
             for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
                 String label = Debug.MemoryInfo.getOtherLabel(j);
                 catMems.add(new MemItem(label, label, miscPss[j], miscSwapPss[j], j));
             }
+            if (dalvikSubitemPss.length > 0) {
+                // Add dalvik subitems.
+                for (MemItem memItem : catMems) {
+                    int memItemStart = 0, memItemEnd = 0;
+                    if (memItem.id == dalvikId) {
+                        memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_START;
+                        memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_END;
+                    } else if (memItem.id == Debug.MemoryInfo.OTHER_DALVIK_OTHER) {
+                        memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_OTHER_START;
+                        memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_OTHER_END;
+                    } else if (memItem.id == Debug.MemoryInfo.OTHER_DEX) {
+                        memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DEX_START;
+                        memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DEX_END;
+                    } else if (memItem.id == Debug.MemoryInfo.OTHER_ART) {
+                        memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_ART_START;
+                        memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_ART_END;
+                    } else {
+                        continue;  // No subitems, continue.
+                    }
+                    memItem.subitems = new ArrayList<MemItem>();
+                    for (int j=memItemStart; j<=memItemEnd; j++) {
+                        final String name = Debug.MemoryInfo.getOtherLabel(
+                                Debug.MemoryInfo.NUM_OTHER_STATS + j);
+                        memItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j],
+                                dalvikSubitemSwapPss[j], j));
+                    }
+                }
+            }
 
             ArrayList<MemItem> oomMems = new ArrayList<MemItem>();
             for (int j=0; j<oomPss.length; j++) {
@@ -21749,6 +21767,9 @@
             }
             if (DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path);
             mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG);
+
+            // Forced gc to clean up the remnant hprof fd.
+            Runtime.getRuntime().gc();
         }
     }
 
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 66347e6..56859e6 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -29,6 +29,7 @@
 import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
 import android.net.ICaptivePortal;
+import android.net.Network;
 import android.net.NetworkRequest;
 import android.net.ProxyInfo;
 import android.net.TrafficStats;
@@ -71,6 +72,9 @@
 import java.net.URL;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
@@ -95,7 +99,7 @@
             "http://play.googleapis.com/generate_204";
     private static final String DEFAULT_USER_AGENT    = "Mozilla/5.0 (X11; Linux x86_64) "
                                                       + "AppleWebKit/537.36 (KHTML, like Gecko) "
-                                                      + "Chrome/52.0.2743.82 Safari/537.36";
+                                                      + "Chrome/60.0.3112.32 Safari/537.36";
 
     private static final int SOCKET_TIMEOUT_MS = 10000;
     private static final int PROBE_TIMEOUT_MS  = 3000;
@@ -228,6 +232,7 @@
     private final Context mContext;
     private final Handler mConnectivityServiceHandler;
     private final NetworkAgentInfo mNetworkAgentInfo;
+    private final Network mNetwork;
     private final int mNetId;
     private final TelephonyManager mTelephonyManager;
     private final WifiManager mWifiManager;
@@ -286,7 +291,8 @@
         mMetricsLog = logger;
         mConnectivityServiceHandler = handler;
         mNetworkAgentInfo = networkAgentInfo;
-        mNetId = mNetworkAgentInfo.network.netId;
+        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);
@@ -415,7 +421,7 @@
             maybeLogEvaluationResult(
                     networkEventType(validationStage(), EvaluationResult.VALIDATED));
             mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
-                    NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null));
+                    NETWORK_TEST_RESULT_VALID, mNetId, null));
             mValidations++;
         }
 
@@ -440,7 +446,8 @@
                 case CMD_LAUNCH_CAPTIVE_PORTAL_APP:
                     final Intent intent = new Intent(
                             ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
-                    intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network);
+                    // OneAddressPerFamilyNetwork is not parcelable across processes.
+                    intent.putExtra(ConnectivityManager.EXTRA_NETWORK, new Network(mNetwork));
                     intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
                             new CaptivePortal(new ICaptivePortal.Stub() {
                                 @Override
@@ -468,8 +475,7 @@
 
         @Override
         public void exit() {
-            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0,
-                    mNetworkAgentInfo.network.netId, null);
+            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetId, null);
             mConnectivityServiceHandler.sendMessage(message);
         }
     }
@@ -623,7 +629,7 @@
         CustomIntentReceiver(String action, int token, int what) {
             mToken = token;
             mWhat = what;
-            mAction = action + "_" + mNetworkAgentInfo.network.netId + "_" + token;
+            mAction = action + "_" + mNetId + "_" + token;
             mContext.registerReceiver(this, new IntentFilter(mAction));
         }
         public PendingIntent getPendingIntent() {
@@ -659,8 +665,7 @@
                         CMD_LAUNCH_CAPTIVE_PORTAL_APP);
             }
             // Display the sign in notification.
-            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
-                    mNetworkAgentInfo.network.netId,
+            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, mNetId,
                     mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent());
             mConnectivityServiceHandler.sendMessage(message);
             // Retest for captive portal occasionally.
@@ -675,6 +680,31 @@
         }
     }
 
+    // Limits the list of IP addresses returned by getAllByName or tried by openConnection to at
+    // most one per address family. This ensures we only wait up to 20 seconds for TCP connections
+    // to complete, regardless of how many IP addresses a host has.
+    private static class OneAddressPerFamilyNetwork extends Network {
+        public OneAddressPerFamilyNetwork(Network network) {
+            super(network);
+        }
+
+        @Override
+        public InetAddress[] getAllByName(String host) throws UnknownHostException {
+            List<InetAddress> addrs = Arrays.asList(super.getAllByName(host));
+
+            // Ensure the address family of the first address is tried first.
+            LinkedHashMap<Class, InetAddress> addressByFamily = new LinkedHashMap<>();
+            addressByFamily.put(addrs.get(0).getClass(), addrs.get(0));
+            Collections.shuffle(addrs);
+
+            for (InetAddress addr : addrs) {
+                addressByFamily.put(addr.getClass(), addr);
+            }
+
+            return addressByFamily.values().toArray(new InetAddress[addressByFamily.size()]);
+        }
+    }
+
     private static String getCaptivePortalServerHttpsUrl(Context context) {
         return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
     }
@@ -805,7 +835,7 @@
         int result;
         String connectInfo;
         try {
-            InetAddress[] addresses = mNetworkAgentInfo.network.getAllByName(host);
+            InetAddress[] addresses = mNetwork.getAllByName(host);
             StringBuffer buffer = new StringBuffer();
             for (InetAddress address : addresses) {
                 buffer.append(',').append(address.getHostAddress());
@@ -833,7 +863,7 @@
         String redirectUrl = null;
         final Stopwatch probeTimer = new Stopwatch().start();
         try {
-            urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
+            urlConnection = (HttpURLConnection) mNetwork.openConnection(url);
             urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC);
             urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
             urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 4ff6657..1bf9733 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -140,6 +140,18 @@
             extraInfo = null;
         }
 
+        // Clear any previous notification with lower priority, otherwise return. http://b/63676954.
+        // A new SIGN_IN notification with a new intent should override any existing one.
+        final int previousEventId = mNotificationTypeMap.get(id);
+        final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+        if (priority(previousNotifyType) > priority(notifyType)) {
+            Slog.d(TAG, String.format(
+                    "ignoring notification %s for network %s with existing notification %s",
+                    notifyType, id, previousNotifyType));
+            return;
+        }
+        clearNotification(id);
+
         if (DBG) {
             Slog.d(TAG, String.format(
                     "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s",
@@ -270,4 +282,22 @@
         NotificationType t = NotificationType.getFromId(eventId);
         return (t != null) ? t.name() : "UNKNOWN";
     }
+
+    private static int priority(NotificationType t) {
+        if (t == null) {
+            return 0;
+        }
+        switch (t) {
+            case SIGN_IN:
+                return 4;
+            case NO_INTERNET:
+                return 3;
+            case NETWORK_SWITCH:
+                return 2;
+            case LOST_INTERNET:
+                return 1;
+            default:
+                return 0;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/OWNERS b/services/core/java/com/android/server/connectivity/OWNERS
new file mode 100644
index 0000000..74f39a1
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/OWNERS
@@ -0,0 +1,5 @@
+set noparent
+
+ek@google.com
+hugobenichi@google.com
+lorenzo@google.com
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 720b750..4574fba 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity;
 
+import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
 import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
 import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -47,6 +48,7 @@
 import android.net.ConnectivityManager;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
+import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -55,6 +57,7 @@
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
@@ -108,6 +111,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 
@@ -211,13 +215,13 @@
         final Handler smHandler = mTetherMasterSM.getHandler();
         mOffloadController = new OffloadController(smHandler,
                 deps.getOffloadHardwareInterface(smHandler, mLog),
-                mContext.getContentResolver(),
+                mContext.getContentResolver(), mNMService,
                 mLog);
         mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
-                mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK, mLog);
+                mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new HashSet<>();
         mSimChange = new SimChangeListener(
-                mContext, mTetherMasterSM.getHandler(), () -> reevaluateSimCardProvisioning());
+                mContext, smHandler, () -> reevaluateSimCardProvisioning());
 
         mStateReceiver = new StateReceiver();
         IntentFilter filter = new IntentFilter();
@@ -225,13 +229,13 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
-        mContext.registerReceiver(mStateReceiver, filter, null, mTetherMasterSM.getHandler());
+        mContext.registerReceiver(mStateReceiver, filter, null, smHandler);
 
         filter = new IntentFilter();
         filter.addAction(Intent.ACTION_MEDIA_SHARED);
         filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
         filter.addDataScheme("file");
-        mContext.registerReceiver(mStateReceiver, filter, null, mTetherMasterSM.getHandler());
+        mContext.registerReceiver(mStateReceiver, filter, null, smHandler);
 
         UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
 
@@ -259,6 +263,12 @@
         mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
     }
 
+    private void maybeUpdateConfiguration() {
+        final int dunCheck = TetheringConfiguration.checkDunRequired(mContext);
+        if (dunCheck == mConfig.dunCheck) return;
+        updateConfiguration();
+    }
+
     @Override
     public void interfaceStatusChanged(String iface, boolean up) {
         // Never called directly: only called from interfaceLinkStateChanged.
@@ -713,7 +723,7 @@
     @VisibleForTesting
     protected void showTetheredNotification(int icon, boolean tetheringOn) {
         NotificationManager notificationManager =
-                (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         if (notificationManager == null) {
             return;
         }
@@ -768,7 +778,7 @@
     @VisibleForTesting
     protected void clearTetheredNotification() {
         NotificationManager notificationManager =
-            (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+            (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         if (notificationManager != null && mLastNotificationId != 0) {
             notificationManager.cancelAsUser(null, mLastNotificationId,
                     UserHandle.ALL);
@@ -807,11 +817,37 @@
 
         private void handleUsbAction(Intent intent) {
             final boolean usbConnected = intent.getBooleanExtra(USB_CONNECTED, false);
+            final boolean usbConfigured = intent.getBooleanExtra(USB_CONFIGURED, false);
             final boolean rndisEnabled = intent.getBooleanExtra(USB_FUNCTION_RNDIS, false);
+
+            mLog.log(String.format("USB bcast connected:%s configured:%s rndis:%s",
+                    usbConnected, usbConfigured, rndisEnabled));
+
+            // There are three types of ACTION_USB_STATE:
+            //
+            //     - DISCONNECTED (USB_CONNECTED and USB_CONFIGURED are 0)
+            //       Meaning: USB connection has ended either because of
+            //       software reset or hard unplug.
+            //
+            //     - CONNECTED (USB_CONNECTED is 1, USB_CONFIGURED is 0)
+            //       Meaning: the first stage of USB protocol handshake has
+            //       occurred but it is not complete.
+            //
+            //     - CONFIGURED (USB_CONNECTED and USB_CONFIGURED are 1)
+            //       Meaning: the USB handshake is completely done and all the
+            //       functions are ready to use.
+            //
+            // For more explanation, see b/62552150 .
+            if (usbConnected && !usbConfigured) {
+                // Nothing for us to do here.
+                // TODO: consider ignoring DISCONNECTED broadcasts as well.
+                return;
+            }
+
             synchronized (Tethering.this.mPublicSync) {
                 mRndisEnabled = rndisEnabled;
                 // start tethering if we have a request pending
-                if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
+                if (usbConfigured && mRndisEnabled && mUsbTetherRequested) {
                     tetherMatchingInterfaces(
                             IControlsTethering.STATE_TETHERED,
                             ConnectivityManager.TETHERING_USB);
@@ -1026,7 +1062,7 @@
 
     public int setUsbTethering(boolean enable) {
         if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
-        UsbManager usbManager = mContext.getSystemService(UsbManager.class);
+        UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
 
         synchronized (mPublicSync) {
             if (enable) {
@@ -1103,11 +1139,8 @@
         return list.toArray(new String[list.size()]);
     }
 
-    private void maybeLogMessage(State state, int what) {
-        if (DBG) {
-            Log.d(TAG, state.getName() + " got " +
-                    sMagicDecoderRing.get(what, Integer.toString(what)));
-        }
+    private void logMessage(State state, int what) {
+        mLog.log(state.getName() + " got " + sMagicDecoderRing.get(what, Integer.toString(what)));
     }
 
     private boolean upstreamWanted() {
@@ -1121,7 +1154,7 @@
     // Needed because the canonical source of upstream truth is just the
     // upstream interface name, |mCurrentUpstreamIface|.  This is ripe for
     // future simplification, once the upstream Network is canonical.
-    boolean pertainsToCurrentUpstream(NetworkState ns) {
+    private boolean pertainsToCurrentUpstream(NetworkState ns) {
         if (ns != null && ns.linkProperties != null && mCurrentUpstreamIface != null) {
             for (String ifname : ns.linkProperties.getAllInterfaceNames()) {
                 if (mCurrentUpstreamIface.equals(ifname)) {
@@ -1172,14 +1205,14 @@
         static final int CMD_CLEAR_ERROR                        = BASE_MASTER + 6;
         static final int EVENT_IFACE_UPDATE_LINKPROPERTIES      = BASE_MASTER + 7;
 
-        private State mInitialState;
-        private State mTetherModeAliveState;
+        private final State mInitialState;
+        private final State mTetherModeAliveState;
 
-        private State mSetIpForwardingEnabledErrorState;
-        private State mSetIpForwardingDisabledErrorState;
-        private State mStartTetheringErrorState;
-        private State mStopTetheringErrorState;
-        private State mSetDnsForwardersErrorState;
+        private final State mSetIpForwardingEnabledErrorState;
+        private final State mSetIpForwardingDisabledErrorState;
+        private final State mStartTetheringErrorState;
+        private final State mStopTetheringErrorState;
+        private final State mSetDnsForwardersErrorState;
 
         // This list is a little subtle.  It contains all the interfaces that currently are
         // requesting tethering, regardless of whether these interfaces are still members of
@@ -1219,22 +1252,46 @@
 
             mNotifyList = new ArrayList<>();
             mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList, mLog);
+
             setInitialState(mInitialState);
         }
 
+        private void startOffloadController() {
+            mOffloadController.start();
+            sendOffloadExemptPrefixes();
+        }
+
+        private void sendOffloadExemptPrefixes() {
+            sendOffloadExemptPrefixes(mUpstreamNetworkMonitor.getLocalPrefixes());
+        }
+
+        private void sendOffloadExemptPrefixes(Set<IpPrefix> localPrefixes) {
+            // Add in well-known minimum set.
+            PrefixUtils.addNonForwardablePrefixes(localPrefixes);
+            // Add tragically hardcoded prefixes.
+            localPrefixes.add(PrefixUtils.DEFAULT_WIFI_P2P_PREFIX);
+
+            // Add prefixes for all downstreams, regardless of IP serving mode.
+            for (TetherInterfaceStateMachine tism : mNotifyList) {
+                localPrefixes.addAll(PrefixUtils.localPrefixesFrom(tism.linkProperties()));
+            }
+
+            mOffloadController.setLocalPrefixes(localPrefixes);
+        }
+
         class InitialState extends State {
             @Override
             public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
+                logMessage(this, message.what);
                 switch (message.what) {
                     case EVENT_IFACE_SERVING_STATE_ACTIVE:
-                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine) message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
                         handleInterfaceServingStateActive(message.arg1, who);
                         transitionTo(mTetherModeAliveState);
                         break;
                     case EVENT_IFACE_SERVING_STATE_INACTIVE:
-                        who = (TetherInterfaceStateMachine)message.obj;
+                        who = (TetherInterfaceStateMachine) message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
                         handleInterfaceServingStateInactive(who);
                         break;
@@ -1248,146 +1305,140 @@
             }
         }
 
-        class TetherMasterUtilState extends State {
-            @Override
-            public boolean processMessage(Message m) {
+        protected boolean turnOnMasterTetherSettings() {
+            final TetheringConfiguration cfg = mConfig;
+            try {
+                mNMService.setIpForwardingEnabled(true);
+            } catch (Exception e) {
+                mLog.e(e);
+                transitionTo(mSetIpForwardingEnabledErrorState);
                 return false;
             }
-
-            protected boolean turnOnMasterTetherSettings() {
-                final TetheringConfiguration cfg = mConfig;
-                try {
-                    mNMService.setIpForwardingEnabled(true);
-                } catch (Exception e) {
-                    mLog.e(e);
-                    transitionTo(mSetIpForwardingEnabledErrorState);
-                    return false;
-                }
-                // TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
-                try {
-                    // TODO: Find a more accurate method name (startDHCPv4()?).
-                    mNMService.startTethering(cfg.dhcpRanges);
-                } catch (Exception e) {
-                    try {
-                        mNMService.stopTethering();
-                        mNMService.startTethering(cfg.dhcpRanges);
-                    } catch (Exception ee) {
-                        mLog.e(ee);
-                        transitionTo(mStartTetheringErrorState);
-                        return false;
-                    }
-                }
-                mLog.log("SET master tether settings: ON");
-                return true;
-            }
-
-            protected boolean turnOffMasterTetherSettings() {
+            // TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
+            try {
+                // TODO: Find a more accurate method name (startDHCPv4()?).
+                mNMService.startTethering(cfg.dhcpRanges);
+            } catch (Exception e) {
                 try {
                     mNMService.stopTethering();
-                } catch (Exception e) {
-                    mLog.e(e);
-                    transitionTo(mStopTetheringErrorState);
+                    mNMService.startTethering(cfg.dhcpRanges);
+                } catch (Exception ee) {
+                    mLog.e(ee);
+                    transitionTo(mStartTetheringErrorState);
                     return false;
                 }
-                try {
-                    mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) {
-                    mLog.e(e);
-                    transitionTo(mSetIpForwardingDisabledErrorState);
-                    return false;
-                }
-                transitionTo(mInitialState);
-                mLog.log("SET master tether settings: OFF");
-                return true;
             }
+            mLog.log("SET master tether settings: ON");
+            return true;
+        }
 
-            protected void chooseUpstreamType(boolean tryCell) {
-                updateConfiguration(); // TODO - remove?
-
-                final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType(
-                        mConfig.preferredUpstreamIfaceTypes);
-                if (ns == null) {
-                    if (tryCell) {
-                        mUpstreamNetworkMonitor.registerMobileNetworkRequest();
-                        // We think mobile should be coming up; don't set a retry.
-                    } else {
-                        sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
-                    }
-                }
-                setUpstreamNetwork(ns);
+        protected boolean turnOffMasterTetherSettings() {
+            try {
+                mNMService.stopTethering();
+            } catch (Exception e) {
+                mLog.e(e);
+                transitionTo(mStopTetheringErrorState);
+                return false;
             }
+            try {
+                mNMService.setIpForwardingEnabled(false);
+            } catch (Exception e) {
+                mLog.e(e);
+                transitionTo(mSetIpForwardingDisabledErrorState);
+                return false;
+            }
+            transitionTo(mInitialState);
+            mLog.log("SET master tether settings: OFF");
+            return true;
+        }
 
-            protected void setUpstreamNetwork(NetworkState ns) {
-                String iface = null;
-                if (ns != null && ns.linkProperties != null) {
-                    // Find the interface with the default IPv4 route. It may be the
-                    // interface described by linkProperties, or one of the interfaces
-                    // stacked on top of it.
-                    Log.i(TAG, "Finding IPv4 upstream interface on: " + ns.linkProperties);
-                    RouteInfo ipv4Default = RouteInfo.selectBestRoute(
-                        ns.linkProperties.getAllRoutes(), Inet4Address.ANY);
-                    if (ipv4Default != null) {
-                        iface = ipv4Default.getInterface();
-                        Log.i(TAG, "Found interface " + ipv4Default.getInterface());
-                    } else {
-                        Log.i(TAG, "No IPv4 upstream interface, giving up.");
-                    }
-                }
+        protected void chooseUpstreamType(boolean tryCell) {
+            // We rebuild configuration on ACTION_CONFIGURATION_CHANGED, but we
+            // do not currently know how to watch for changes in DUN settings.
+            maybeUpdateConfiguration();
 
-                if (iface != null) {
-                    setDnsForwarders(ns.network, ns.linkProperties);
+            final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType(
+                    mConfig.preferredUpstreamIfaceTypes);
+            if (ns == null) {
+                if (tryCell) {
+                    mUpstreamNetworkMonitor.registerMobileNetworkRequest();
+                    // We think mobile should be coming up; don't set a retry.
+                } else {
+                    sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
                 }
-                notifyTetheredOfNewUpstreamIface(iface);
-                if (ns != null && pertainsToCurrentUpstream(ns)) {
-                    // If we already have NetworkState for this network examine
-                    // it immediately, because there likely will be no second
-                    // EVENT_ON_AVAILABLE (it was already received).
-                    handleNewUpstreamNetworkState(ns);
-                } else if (mCurrentUpstreamIface == null) {
-                    // There are no available upstream networks, or none that
-                    // have an IPv4 default route (current metric for success).
-                    handleNewUpstreamNetworkState(null);
+            }
+            setUpstreamNetwork(ns);
+        }
+
+        protected void setUpstreamNetwork(NetworkState ns) {
+            String iface = null;
+            if (ns != null && ns.linkProperties != null) {
+                // Find the interface with the default IPv4 route. It may be the
+                // interface described by linkProperties, or one of the interfaces
+                // stacked on top of it.
+                mLog.i("Finding IPv4 upstream interface on: " + ns.linkProperties);
+                RouteInfo ipv4Default = RouteInfo.selectBestRoute(
+                    ns.linkProperties.getAllRoutes(), Inet4Address.ANY);
+                if (ipv4Default != null) {
+                    iface = ipv4Default.getInterface();
+                    mLog.i("Found interface " + ipv4Default.getInterface());
+                } else {
+                    mLog.i("No IPv4 upstream interface, giving up.");
                 }
             }
 
-            protected void setDnsForwarders(final Network network, final LinkProperties lp) {
-                // TODO: Set v4 and/or v6 DNS per available connectivity.
-                String[] dnsServers = mConfig.defaultIPv4DNS;
-                final Collection<InetAddress> dnses = lp.getDnsServers();
-                // TODO: Properly support the absence of DNS servers.
-                if (dnses != null && !dnses.isEmpty()) {
-                    // TODO: remove this invocation of NetworkUtils.makeStrings().
-                    dnsServers = NetworkUtils.makeStrings(dnses);
-                }
-                try {
-                    mNMService.setDnsForwarders(network, dnsServers);
-                    mLog.log(String.format(
-                            "SET DNS forwarders: network=%s dnsServers=%s",
-                            network, Arrays.toString(dnsServers)));
-                } catch (Exception e) {
-                    // TODO: Investigate how this can fail and what exactly
-                    // happens if/when such failures occur.
-                    mLog.e("setting DNS forwarders failed, " + e);
-                    transitionTo(mSetDnsForwardersErrorState);
-                }
+            if (iface != null) {
+                setDnsForwarders(ns.network, ns.linkProperties);
             }
+            notifyDownstreamsOfNewUpstreamIface(iface);
+            if (ns != null && pertainsToCurrentUpstream(ns)) {
+                // If we already have NetworkState for this network examine
+                // it immediately, because there likely will be no second
+                // EVENT_ON_AVAILABLE (it was already received).
+                handleNewUpstreamNetworkState(ns);
+            } else if (mCurrentUpstreamIface == null) {
+                // There are no available upstream networks, or none that
+                // have an IPv4 default route (current metric for success).
+                handleNewUpstreamNetworkState(null);
+            }
+        }
 
-            protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
-                if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName);
-                mCurrentUpstreamIface = ifaceName;
-                for (TetherInterfaceStateMachine sm : mNotifyList) {
-                    sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
-                            ifaceName);
-                }
+        protected void setDnsForwarders(final Network network, final LinkProperties lp) {
+            // TODO: Set v4 and/or v6 DNS per available connectivity.
+            String[] dnsServers = mConfig.defaultIPv4DNS;
+            final Collection<InetAddress> dnses = lp.getDnsServers();
+            // TODO: Properly support the absence of DNS servers.
+            if (dnses != null && !dnses.isEmpty()) {
+                // TODO: remove this invocation of NetworkUtils.makeStrings().
+                dnsServers = NetworkUtils.makeStrings(dnses);
             }
+            try {
+                mNMService.setDnsForwarders(network, dnsServers);
+                mLog.log(String.format(
+                        "SET DNS forwarders: network=%s dnsServers=%s",
+                        network, Arrays.toString(dnsServers)));
+            } catch (Exception e) {
+                // TODO: Investigate how this can fail and what exactly
+                // happens if/when such failures occur.
+                mLog.e("setting DNS forwarders failed, " + e);
+                transitionTo(mSetDnsForwardersErrorState);
+            }
+        }
 
-            protected void handleNewUpstreamNetworkState(NetworkState ns) {
-                mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
-                mOffloadController.setUpstreamLinkProperties(
-                        (ns != null) ? ns.linkProperties : null);
+        protected void notifyDownstreamsOfNewUpstreamIface(String ifaceName) {
+            mLog.log("Notifying downstreams of upstream=" + ifaceName);
+            mCurrentUpstreamIface = ifaceName;
+            for (TetherInterfaceStateMachine sm : mNotifyList) {
+                sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
+                        ifaceName);
             }
         }
 
+        protected void handleNewUpstreamNetworkState(NetworkState ns) {
+            mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
+            mOffloadController.setUpstreamLinkProperties((ns != null) ? ns.linkProperties : null);
+        }
+
         private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) {
             if (mNotifyList.indexOf(who) < 0) {
                 mNotifyList.add(who);
@@ -1434,7 +1485,61 @@
             }
         }
 
-        class TetherModeAliveState extends TetherMasterUtilState {
+        private void handleUpstreamNetworkMonitorCallback(int arg1, Object o) {
+            if (arg1 == UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES) {
+                sendOffloadExemptPrefixes((Set<IpPrefix>) o);
+                return;
+            }
+
+            final NetworkState ns = (NetworkState) o;
+
+            if (ns == null || !pertainsToCurrentUpstream(ns)) {
+                // TODO: In future, this is where upstream evaluation and selection
+                // could be handled for notifications which include sufficient data.
+                // For example, after CONNECTIVITY_ACTION listening is removed, here
+                // is where we could observe a Wi-Fi network becoming available and
+                // passing validation.
+                if (mCurrentUpstreamIface == null) {
+                    // If we have no upstream interface, try to run through upstream
+                    // selection again.  If, for example, IPv4 connectivity has shown up
+                    // after IPv6 (e.g., 464xlat became available) we want the chance to
+                    // notice and act accordingly.
+                    chooseUpstreamType(false);
+                }
+                return;
+            }
+
+            switch (arg1) {
+                case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE:
+                    // The default network changed, or DUN connected
+                    // before this callback was processed. Updates
+                    // for the current NetworkCapabilities and
+                    // LinkProperties have been requested (default
+                    // request) or are being sent shortly (DUN). Do
+                    // nothing until they arrive; if no updates
+                    // arrive there's nothing to do.
+                    break;
+                case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES:
+                    handleNewUpstreamNetworkState(ns);
+                    break;
+                case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
+                    setDnsForwarders(ns.network, ns.linkProperties);
+                    handleNewUpstreamNetworkState(ns);
+                    break;
+                case UpstreamNetworkMonitor.EVENT_ON_LOST:
+                    // TODO: Re-evaluate possible upstreams. Currently upstream
+                    // reevaluation is triggered via received CONNECTIVITY_ACTION
+                    // broadcasts that result in being passed a
+                    // TetherMasterSM.CMD_UPSTREAM_CHANGED.
+                    handleNewUpstreamNetworkState(null);
+                    break;
+                default:
+                    mLog.e("Unknown arg1 value: " + arg1);
+                    break;
+            }
+        }
+
+        class TetherModeAliveState extends State {
             boolean mUpstreamWanted = false;
             boolean mTryCell = true;
 
@@ -1452,7 +1557,7 @@
                 // TODO: De-duplicate with updateUpstreamWanted() below.
                 if (upstreamWanted()) {
                     mUpstreamWanted = true;
-                    mOffloadController.start();
+                    startOffloadController();
                     chooseUpstreamType(true);
                     mTryCell = false;
                 }
@@ -1463,7 +1568,7 @@
                 mOffloadController.stop();
                 mUpstreamNetworkMonitor.stop();
                 mSimChange.stopListening();
-                notifyTetheredOfNewUpstreamIface(null);
+                notifyDownstreamsOfNewUpstreamIface(null);
                 handleNewUpstreamNetworkState(null);
             }
 
@@ -1472,7 +1577,7 @@
                 mUpstreamWanted = upstreamWanted();
                 if (mUpstreamWanted != previousUpstreamWanted) {
                     if (mUpstreamWanted) {
-                        mOffloadController.start();
+                        startOffloadController();
                     } else {
                         mOffloadController.stop();
                     }
@@ -1482,11 +1587,11 @@
 
             @Override
             public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
+                logMessage(this, message.what);
                 boolean retValue = true;
                 switch (message.what) {
                     case EVENT_IFACE_SERVING_STATE_ACTIVE: {
-                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine) message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
                         handleInterfaceServingStateActive(message.arg1, who);
                         who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
@@ -1500,7 +1605,7 @@
                         break;
                     }
                     case EVENT_IFACE_SERVING_STATE_INACTIVE: {
-                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine) message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
                         handleInterfaceServingStateInactive(who);
 
@@ -1532,6 +1637,9 @@
                             mOffloadController.notifyDownstreamLinkProperties(newLp);
                         } else {
                             mOffloadController.removeDownstreamInterface(newLp.getInterfaceName());
+                            // Another interface might be in local-only hotspot mode;
+                            // resend all local prefixes to the OffloadController.
+                            sendOffloadExemptPrefixes();
                         }
                         break;
                     }
@@ -1552,52 +1660,8 @@
                         break;
                     case EVENT_UPSTREAM_CALLBACK: {
                         updateUpstreamWanted();
-                        if (!mUpstreamWanted) break;
-
-                        final NetworkState ns = (NetworkState) message.obj;
-
-                        if (ns == null || !pertainsToCurrentUpstream(ns)) {
-                            // TODO: In future, this is where upstream evaluation and selection
-                            // could be handled for notifications which include sufficient data.
-                            // For example, after CONNECTIVITY_ACTION listening is removed, here
-                            // is where we could observe a Wi-Fi network becoming available and
-                            // passing validation.
-                            if (mCurrentUpstreamIface == null) {
-                                // If we have no upstream interface, try to run through upstream
-                                // selection again.  If, for example, IPv4 connectivity has shown up
-                                // after IPv6 (e.g., 464xlat became available) we want the chance to
-                                // notice and act accordingly.
-                                chooseUpstreamType(false);
-                            }
-                            break;
-                        }
-
-                        switch (message.arg1) {
-                            case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE:
-                                // The default network changed, or DUN connected
-                                // before this callback was processed. Updates
-                                // for the current NetworkCapabilities and
-                                // LinkProperties have been requested (default
-                                // request) or are being sent shortly (DUN). Do
-                                // nothing until they arrive; if no updates
-                                // arrive there's nothing to do.
-                                break;
-                            case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES:
-                                handleNewUpstreamNetworkState(ns);
-                                break;
-                            case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
-                                setDnsForwarders(ns.network, ns.linkProperties);
-                                handleNewUpstreamNetworkState(ns);
-                                break;
-                            case UpstreamNetworkMonitor.EVENT_ON_LOST:
-                                // TODO: Re-evaluate possible upstreams. Currently upstream
-                                // reevaluation is triggered via received CONNECTIVITY_ACTION
-                                // broadcasts that result in being passed a
-                                // TetherMasterSM.CMD_UPSTREAM_CHANGED.
-                                handleNewUpstreamNetworkState(null);
-                                break;
-                            default:
-                                break;
+                        if (mUpstreamWanted) {
+                            handleUpstreamNetworkMonitorCallback(message.arg1, message.obj);
                         }
                         break;
                     }
@@ -1617,7 +1681,7 @@
                 boolean retValue = true;
                 switch (message.what) {
                     case EVENT_IFACE_SERVING_STATE_ACTIVE:
-                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine) message.obj;
                         who.sendMessage(mErrorNotification);
                         break;
                     case CMD_CLEAR_ERROR:
@@ -1746,6 +1810,11 @@
             pw.decreaseIndent();
         }
 
+        pw.println("Hardware offload:");
+        pw.increaseIndent();
+        mOffloadController.dump(pw);
+        pw.decreaseIndent();
+
         pw.println("Log:");
         pw.increaseIndent();
         if (argsContain(args, SHORT_ARG)) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 08deef8..1a5ff77 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -16,18 +16,38 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
 import android.content.ContentResolver;
+import android.net.ITetheringStatsProvider;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkStats;
 import android.net.RouteInfo;
 import android.net.util.SharedLog;
 import android.os.Handler;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.SystemClock;
 import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
 
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A class to encapsulate the business logic of programming the tethering
@@ -38,6 +58,8 @@
 public class OffloadController {
     private static final String TAG = OffloadController.class.getSimpleName();
 
+    private static final int STATS_FETCH_TIMEOUT_MS = 1000;
+
     private final Handler mHandler;
     private final OffloadHardwareInterface mHwInterface;
     private final ContentResolver mContentResolver;
@@ -45,17 +67,42 @@
     private boolean mConfigInitialized;
     private boolean mControlInitialized;
     private LinkProperties mUpstreamLinkProperties;
+    // The complete set of offload-exempt prefixes passed in via Tethering from
+    // all upstream and downstream sources.
+    private Set<IpPrefix> mExemptPrefixes;
+    // A strictly "smaller" set of prefixes, wherein offload-approved prefixes
+    // (e.g. downstream on-link prefixes) have been removed and replaced with
+    // prefixes representing only the locally-assigned IP addresses.
+    private Set<String> mLastLocalPrefixStrs;
+
+    // Maps upstream interface names to offloaded traffic statistics.
+    private HashMap<String, OffloadHardwareInterface.ForwardedStats>
+            mForwardedStats = new HashMap<>();
 
     public OffloadController(Handler h, OffloadHardwareInterface hwi,
-            ContentResolver contentResolver, SharedLog log) {
+            ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
         mHandler = h;
         mHwInterface = hwi;
         mContentResolver = contentResolver;
         mLog = log.forSubComponent(TAG);
+        mExemptPrefixes = new HashSet<>();
+        mLastLocalPrefixStrs = new HashSet<>();
+
+        try {
+            nms.registerTetheringStatsProvider(
+                    new OffloadTetheringStatsProvider(), getClass().getSimpleName());
+        } catch (RemoteException e) {
+            mLog.e("Cannot register offload stats provider: " + e);
+        }
     }
 
     public void start() {
-        if (isOffloadDisabled() || started()) return;
+        if (started()) return;
+
+        if (isOffloadDisabled()) {
+            mLog.i("tethering offload disabled");
+            return;
+        }
 
         if (!mConfigInitialized) {
             mConfigInitialized = mHwInterface.initOffloadConfig();
@@ -69,8 +116,36 @@
         mControlInitialized = mHwInterface.initOffloadControl(
                 new OffloadHardwareInterface.ControlCallback() {
                     @Override
-                    public void onOffloadEvent(int event) {
-                        mLog.log("got offload event: " + event);
+                    public void onStarted() {
+                        mLog.log("onStarted");
+                    }
+
+                    @Override
+                    public void onStoppedError() {
+                        mLog.log("onStoppedError");
+                    }
+
+                    @Override
+                    public void onStoppedUnsupported() {
+                        mLog.log("onStoppedUnsupported");
+                    }
+
+                    @Override
+                    public void onSupportAvailable() {
+                        mLog.log("onSupportAvailable");
+
+                        // [1] Poll for statistics and notify NetworkStats
+                        // [2] (Re)Push all state:
+                        //     [a] push local prefixes
+                        //     [b] push downstreams
+                        //     [c] push upstream parameters
+                        pushUpstreamParameters();
+                    }
+
+                    @Override
+                    public void onStoppedLimitReached() {
+                        mLog.log("onStoppedLimitReached");
+                        // Poll for statistics and notify NetworkStats
                     }
 
                     @Override
@@ -90,6 +165,7 @@
 
     public void stop() {
         final boolean wasStarted = started();
+        updateStatsForCurrentUpstream();
         mUpstreamLinkProperties = null;
         mHwInterface.stopOffloadControl();
         mControlInitialized = false;
@@ -97,15 +173,83 @@
         if (wasStarted) mLog.log("tethering offload stopped");
     }
 
+    private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
+        @Override
+        public NetworkStats getTetherStats() {
+            NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
+            CountDownLatch latch = new CountDownLatch(1);
+
+            mHandler.post(() -> {
+                try {
+                    NetworkStats.Entry entry = new NetworkStats.Entry();
+                    entry.set = SET_DEFAULT;
+                    entry.tag = TAG_NONE;
+                    entry.uid = UID_TETHERING;
+
+                    updateStatsForCurrentUpstream();
+
+                    for (String iface : mForwardedStats.keySet()) {
+                        entry.iface = iface;
+                        entry.rxBytes = mForwardedStats.get(iface).rxBytes;
+                        entry.txBytes = mForwardedStats.get(iface).txBytes;
+                        stats.addValues(entry);
+                    }
+                } finally {
+                    latch.countDown();
+                }
+            });
+
+            try {
+                latch.await(STATS_FETCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                mLog.e("Tethering stats fetch timed out after " + STATS_FETCH_TIMEOUT_MS + "ms");
+            }
+
+            return stats;
+        }
+    }
+
+    private void maybeUpdateStats(String iface) {
+        if (TextUtils.isEmpty(iface)) {
+            return;
+        }
+
+        if (!mForwardedStats.containsKey(iface)) {
+            mForwardedStats.put(iface, new OffloadHardwareInterface.ForwardedStats());
+        }
+        mForwardedStats.get(iface).add(mHwInterface.getForwardedStats(iface));
+    }
+
+    private void updateStatsForCurrentUpstream() {
+        if (mUpstreamLinkProperties != null) {
+            maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName());
+        }
+    }
+
     public void setUpstreamLinkProperties(LinkProperties lp) {
-        if (!started()) return;
+        if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
+
+        String prevUpstream = (mUpstreamLinkProperties != null) ?
+                mUpstreamLinkProperties.getInterfaceName() : null;
 
         mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null;
+
         // TODO: examine return code and decide what to do if programming
         // upstream parameters fails (probably just wait for a subsequent
         // onOffloadEvent() callback to tell us offload is available again and
         // then reapply all state).
+        computeAndPushLocalPrefixes();
         pushUpstreamParameters();
+
+        // Update stats after we've told the hardware to change routing so we don't miss packets.
+        maybeUpdateStats(prevUpstream);
+    }
+
+    public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
+        if (!started()) return;
+
+        mExemptPrefixes = localPrefixes;
+        computeAndPushLocalPrefixes();
     }
 
     public void notifyDownstreamLinkProperties(LinkProperties lp) {
@@ -123,8 +267,9 @@
     }
 
     private boolean isOffloadDisabled() {
-        // Defaults to |false| if not present.
-        return (Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0) != 0);
+        final int defaultDisposition = mHwInterface.getDefaultTetherOffloadDisabled();
+        return (Settings.Global.getInt(
+                mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0);
     }
 
     private boolean started() {
@@ -167,4 +312,54 @@
         return mHwInterface.setUpstreamParameters(
                 iface, v4addr, v4gateway, (v6gateways.isEmpty() ? null : v6gateways));
     }
+
+    private boolean computeAndPushLocalPrefixes() {
+        final Set<String> localPrefixStrs = computeLocalPrefixStrings(
+                mExemptPrefixes, mUpstreamLinkProperties);
+        if (mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
+
+        mLastLocalPrefixStrs = localPrefixStrs;
+        return mHwInterface.setLocalPrefixes(new ArrayList<>(localPrefixStrs));
+    }
+
+    // TODO: Factor in downstream LinkProperties once that information is available.
+    private static Set<String> computeLocalPrefixStrings(
+            Set<IpPrefix> localPrefixes, LinkProperties upstreamLinkProperties) {
+        // Create an editable copy.
+        final Set<IpPrefix> prefixSet = new HashSet<>(localPrefixes);
+
+        // TODO: If a downstream interface (not currently passed in) is reusing
+        // the /64 of the upstream (64share) then:
+        //
+        //     [a] remove that /64 from the local prefixes
+        //     [b] add in /128s for IP addresses on the downstream interface
+        //     [c] add in /128s for IP addresses on the upstream interface
+        //
+        // Until downstream information is available here, simply add /128s from
+        // the upstream network; they'll just be redundant with their /64.
+        if (upstreamLinkProperties != null) {
+            for (LinkAddress linkAddr : upstreamLinkProperties.getLinkAddresses()) {
+                if (!linkAddr.isGlobalPreferred()) continue;
+                final InetAddress ip = linkAddr.getAddress();
+                if (!(ip instanceof Inet6Address)) continue;
+                prefixSet.add(new IpPrefix(ip, 128));
+            }
+        }
+
+        final HashSet<String> localPrefixStrs = new HashSet<>();
+        for (IpPrefix pfx : prefixSet) localPrefixStrs.add(pfx.toString());
+        return localPrefixStrs;
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+        if (isOffloadDisabled()) {
+            pw.println("Offload disabled");
+            return;
+        }
+        pw.println("Offload HALs " + (started() ? "started" : "not started"));
+        LinkProperties lp = mUpstreamLinkProperties;
+        String upstream = (lp != null) ? lp.getInterfaceName() : null;
+        pw.println("Current upstream: " + upstream);
+        pw.println("Exempt prefixes: " + mLastLocalPrefixStrs);
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 1fc1684..4df566f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -21,6 +21,7 @@
 import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
 import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
 import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.net.util.SharedLog;
@@ -35,6 +36,11 @@
  */
 public class OffloadHardwareInterface {
     private static final String TAG = OffloadHardwareInterface.class.getSimpleName();
+    private static final String YIELDS = " -> ";
+    // Change this value to control whether tether offload is enabled or
+    // disabled by default in the absence of an explicit Settings value.
+    // See accompanying unittest to distinguish 0 from non-0 values.
+    private static final int DEFAULT_TETHER_OFFLOAD_DISABLED = 0;
     private static final String NO_INTERFACE_NAME = "";
     private static final String NO_IPV4_ADDRESS = "";
     private static final String NO_IPV4_GATEWAY = "";
@@ -48,18 +54,45 @@
     private ControlCallback mControlCallback;
 
     public static class ControlCallback {
-        public void onOffloadEvent(int event) {}
+        public void onStarted() {}
+        public void onStoppedError() {}
+        public void onStoppedUnsupported() {}
+        public void onSupportAvailable() {}
+        public void onStoppedLimitReached() {}
 
         public void onNatTimeoutUpdate(int proto,
                                        String srcAddr, int srcPort,
                                        String dstAddr, int dstPort) {}
     }
 
+    public static class ForwardedStats {
+        public long rxBytes;
+        public long txBytes;
+
+        public ForwardedStats() {
+            rxBytes = 0;
+            txBytes = 0;
+        }
+
+        public void add(ForwardedStats other) {
+            rxBytes += other.rxBytes;
+            txBytes += other.txBytes;
+        }
+
+        public String toString() {
+            return String.format("rx:%s tx:%s", rxBytes, txBytes);
+        }
+    }
+
     public OffloadHardwareInterface(Handler h, SharedLog log) {
         mHandler = h;
         mLog = log.forSubComponent(TAG);
     }
 
+    public int getDefaultTetherOffloadDisabled() {
+        return DEFAULT_TETHER_OFFLOAD_DISABLED;
+    }
+
     public boolean initOffloadConfig() {
         return configOffload();
     }
@@ -76,7 +109,11 @@
             }
         }
 
-        mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, mControlCallback);
+        final String logmsg = String.format("initOffloadControl(%s)",
+                (controlCb == null) ? "null"
+                        : "0x" + Integer.toHexString(System.identityHashCode(controlCb)));
+
+        mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, mControlCallback, mLog);
         final CbResults results = new CbResults();
         try {
             mOffloadControl.initOffload(
@@ -86,11 +123,11 @@
                         results.errMsg = errMsg;
                     });
         } catch (RemoteException e) {
-            mLog.e("failed to initOffload: " + e);
+            record(logmsg, e);
             return false;
         }
 
-        if (!results.success) mLog.e("initOffload failed: " + results.errMsg);
+        record(logmsg, results);
         return results.success;
     }
 
@@ -108,14 +145,58 @@
         mOffloadControl = null;
         mTetheringOffloadCallback = null;
         mControlCallback = null;
+        mLog.log("stopOffloadControl()");
+    }
+
+    public ForwardedStats getForwardedStats(String upstream) {
+        final String logmsg = String.format("getForwardedStats(%s)",  upstream);
+
+        final ForwardedStats stats = new ForwardedStats();
+        try {
+            mOffloadControl.getForwardedStats(
+                    upstream,
+                    (long rxBytes, long txBytes) -> {
+                        stats.rxBytes = (rxBytes > 0) ? rxBytes : 0;
+                        stats.txBytes = (txBytes > 0) ? txBytes : 0;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return stats;
+        }
+
+        mLog.log(logmsg + YIELDS + stats);
+        return stats;
+    }
+
+    public boolean setLocalPrefixes(ArrayList<String> localPrefixes) {
+        final String logmsg = String.format("setLocalPrefixes([%s])",
+                String.join(",", localPrefixes));
+
+        final CbResults results = new CbResults();
+        try {
+            mOffloadControl.setLocalPrefixes(localPrefixes,
+                    (boolean success, String errMsg) -> {
+                        results.success = success;
+                        results.errMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.success;
     }
 
     public boolean setUpstreamParameters(
             String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) {
-        iface = iface != null ? iface : NO_INTERFACE_NAME;
-        v4addr = v4addr != null ? v4addr : NO_IPV4_ADDRESS;
-        v4gateway = v4gateway != null ? v4gateway : NO_IPV4_GATEWAY;
-        v6gws = v6gws != null ? v6gws : new ArrayList<>();
+        iface = (iface != null) ? iface : NO_INTERFACE_NAME;
+        v4addr = (v4addr != null) ? v4addr : NO_IPV4_ADDRESS;
+        v4gateway = (v4gateway != null) ? v4gateway : NO_IPV4_GATEWAY;
+        v6gws = (v6gws != null) ? v6gws : new ArrayList<>();
+
+        final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
+                iface, v4addr, v4gateway, String.join(",", v6gws));
 
         final CbResults results = new CbResults();
         try {
@@ -126,26 +207,61 @@
                         results.errMsg = errMsg;
                     });
         } catch (RemoteException e) {
-            mLog.e("failed to setUpstreamParameters: " + e);
+            record(logmsg, e);
             return false;
         }
 
-        if (!results.success) mLog.e("setUpstreamParameters failed: " + results.errMsg);
+        record(logmsg, results);
         return results.success;
     }
 
+    private void record(String msg, Throwable t) {
+        mLog.e(msg + YIELDS + "exception: " + t);
+    }
+
+    private void record(String msg, CbResults results) {
+        final String logmsg = msg + YIELDS + results;
+        if (!results.success) {
+            mLog.e(logmsg);
+        } else {
+            mLog.log(logmsg);
+        }
+    }
+
     private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
         public final Handler handler;
         public final ControlCallback controlCb;
+        public final SharedLog log;
 
-        public TetheringOffloadCallback(Handler h, ControlCallback cb) {
+        public TetheringOffloadCallback(Handler h, ControlCallback cb, SharedLog sharedLog) {
             handler = h;
             controlCb = cb;
+            log = sharedLog;
         }
 
         @Override
         public void onEvent(int event) {
-            handler.post(() -> { controlCb.onOffloadEvent(event); });
+            handler.post(() -> {
+                switch (event) {
+                    case OffloadCallbackEvent.OFFLOAD_STARTED:
+                        controlCb.onStarted();
+                        break;
+                    case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
+                        controlCb.onStoppedError();
+                        break;
+                    case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
+                        controlCb.onStoppedUnsupported();
+                        break;
+                    case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
+                        controlCb.onSupportAvailable();
+                        break;
+                    case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
+                        controlCb.onStoppedLimitReached();
+                        break;
+                    default:
+                        log.e("Unsupported OffloadCallbackEvent: " + event);
+                }
+            });
         }
 
         @Override
@@ -162,5 +278,13 @@
     private static class CbResults {
         boolean success;
         String errMsg;
+
+        public String toString() {
+            if (success) {
+                return "ok";
+            } else {
+                return "fail: " + errMsg;
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 86b2551..69678df 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -161,6 +161,8 @@
 
     public int lastError() { return mLastError; }
 
+    public LinkProperties linkProperties() { return new LinkProperties(mLinkProperties); }
+
     public void stop() { sendMessage(CMD_INTERFACE_DOWN); }
 
     public void unwanted() { sendMessage(CMD_TETHER_UNREQUESTED); }
@@ -441,12 +443,8 @@
         mLastRaParams = newParams;
     }
 
-    private void maybeLogMessage(State state, int what) {
-        if (DBG) {
-            Log.d(TAG, state.getName() + " got " +
-                    sMagicDecoderRing.get(what, Integer.toString(what)) + ", Iface = " +
-                    mIfaceName);
-        }
+    private void logMessage(State state, int what) {
+        mLog.log(state.getName() + " got " + sMagicDecoderRing.get(what, Integer.toString(what)));
     }
 
     private void sendInterfaceState(int newInterfaceState) {
@@ -473,7 +471,7 @@
 
         @Override
         public boolean processMessage(Message message) {
-            maybeLogMessage(this, message.what);
+            logMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
                     mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
@@ -545,7 +543,7 @@
 
         @Override
         public boolean processMessage(Message message) {
-            maybeLogMessage(this, message.what);
+            logMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_UNREQUESTED:
                     transitionTo(mInitialState);
@@ -595,7 +593,7 @@
         public boolean processMessage(Message message) {
             if (super.processMessage(message)) return true;
 
-            maybeLogMessage(this, message.what);
+            logMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
                     mLog.e("CMD_TETHER_REQUESTED while in local-only hotspot mode.");
@@ -667,7 +665,7 @@
         public boolean processMessage(Message message) {
             if (super.processMessage(message)) return true;
 
-            maybeLogMessage(this, message.what);
+            logMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
                     mLog.e("CMD_TETHER_REQUESTED while already tethering.");
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 9b034ae..eb18b12 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -70,6 +70,7 @@
     public final String[] tetherableUsbRegexs;
     public final String[] tetherableWifiRegexs;
     public final String[] tetherableBluetoothRegexs;
+    public final int dunCheck;
     public final boolean isDunRequired;
     public final Collection<Integer> preferredUpstreamIfaceTypes;
     public final String[] dhcpRanges;
@@ -85,7 +86,7 @@
         tetherableBluetoothRegexs = ctx.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_bluetooth_regexs);
 
-        final int dunCheck = checkDunRequired(ctx);
+        dunCheck = checkDunRequired(ctx);
         configLog.log("DUN check returned: " + dunCheckString(dunCheck));
 
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, dunCheck);
@@ -172,7 +173,7 @@
         return upstreamNames;
     }
 
-    private static int checkDunRequired(Context ctx) {
+    public static int checkDunRequired(Context ctx) {
         final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
         return (tm != null) ? tm.getTetherApnRequired() : DUN_UNSPECIFIED;
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 9ebfaf7..c5f7528 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -26,18 +26,25 @@
 import android.os.Looper;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.NetworkState;
+import android.net.util.NetworkConstants;
+import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.StateMachine;
 
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
 
 
 /**
@@ -70,6 +77,7 @@
     public static final int EVENT_ON_CAPABILITIES   = 2;
     public static final int EVENT_ON_LINKPROPERTIES = 3;
     public static final int EVENT_ON_LOST           = 4;
+    public static final int NOTIFY_LOCAL_PREFIXES   = 10;
 
     private static final int CALLBACK_LISTEN_ALL = 1;
     private static final int CALLBACK_TRACK_DEFAULT = 2;
@@ -81,6 +89,7 @@
     private final Handler mHandler;
     private final int mWhat;
     private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
+    private HashSet<IpPrefix> mLocalPrefixes;
     private ConnectivityManager mCM;
     private NetworkCallback mListenAllCallback;
     private NetworkCallback mDefaultNetworkCallback;
@@ -88,18 +97,19 @@
     private boolean mDunRequired;
     private Network mCurrentDefault;
 
-    public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what, SharedLog log) {
+    public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
         mContext = ctx;
         mTarget = tgt;
         mHandler = mTarget.getHandler();
-        mWhat = what;
         mLog = log.forSubComponent(TAG);
+        mWhat = what;
+        mLocalPrefixes = new HashSet<>();
     }
 
     @VisibleForTesting
     public UpstreamNetworkMonitor(
-            StateMachine tgt, int what, ConnectivityManager cm, SharedLog log) {
-        this(null, tgt, what, log);
+            ConnectivityManager cm, StateMachine tgt, SharedLog log, int what) {
+        this((Context) null, tgt, log, what);
         mCM = cm;
     }
 
@@ -209,6 +219,10 @@
         return typeStatePair.ns;
     }
 
+    public Set<IpPrefix> getLocalPrefixes() {
+        return (Set<IpPrefix>) mLocalPrefixes.clone();
+    }
+
     private void handleAvailable(int callbackType, Network network) {
         if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
 
@@ -342,6 +356,14 @@
         notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
     }
 
+    private void recomputeLocalPrefixes() {
+        final HashSet<IpPrefix> localPrefixes = allLocalPrefixes(mNetworkMap.values());
+        if (!mLocalPrefixes.equals(localPrefixes)) {
+            mLocalPrefixes = localPrefixes;
+            notifyTarget(NOTIFY_LOCAL_PREFIXES, localPrefixes.clone());
+        }
+    }
+
     // Fetch (and cache) a ConnectivityManager only if and when we need one.
     private ConnectivityManager cm() {
         if (mCM == null) {
@@ -376,6 +398,7 @@
         @Override
         public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
             handleLinkProp(network, newLp);
+            recomputeLocalPrefixes();
         }
 
         // TODO: Handle onNetworkSuspended();
@@ -384,6 +407,7 @@
         @Override
         public void onLost(Network network) {
             handleLost(mCallbackType, network);
+            recomputeLocalPrefixes();
         }
     }
 
@@ -395,16 +419,16 @@
         notifyTarget(which, mNetworkMap.get(network));
     }
 
-    private void notifyTarget(int which, NetworkState netstate) {
-        mTarget.sendMessage(mWhat, which, 0, netstate);
+    private void notifyTarget(int which, Object obj) {
+        mTarget.sendMessage(mWhat, which, 0, obj);
     }
 
-    static private class TypeStatePair {
+    private static class TypeStatePair {
         public int type = TYPE_NONE;
         public NetworkState ns = null;
     }
 
-    static private TypeStatePair findFirstAvailableUpstreamByType(
+    private static TypeStatePair findFirstAvailableUpstreamByType(
             Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) {
         final TypeStatePair result = new TypeStatePair();
 
@@ -431,4 +455,16 @@
 
         return result;
     }
+
+    private static HashSet<IpPrefix> allLocalPrefixes(Iterable<NetworkState> netStates) {
+        final HashSet<IpPrefix> prefixSet = new HashSet<>();
+
+        for (NetworkState ns : netStates) {
+            final LinkProperties lp = ns.linkProperties;
+            if (lp == null) continue;
+            prefixSet.addAll(PrefixUtils.localPrefixesFrom(lp));
+        }
+
+        return prefixSet;
+    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index 34c5283..4265741 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -40,6 +40,7 @@
 import org.json.JSONObject;
 
 import java.io.PrintWriter;
+import java.lang.Math;
 import java.util.ArrayDeque;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
@@ -718,8 +719,8 @@
         }
 
         void increment(int imp) {
-            imp = imp < 0 ? 0 : imp > NUM_IMPORTANCES ? NUM_IMPORTANCES : imp;
-            mCount[imp] ++;
+            imp = Math.max(0, Math.min(imp, mCount.length - 1));
+            mCount[imp]++;
         }
 
         void maybeCount(ImportanceHistogram prev) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 49639b0..f55d8e7 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -405,7 +405,8 @@
             boolean newProfile) {
         int dexoptNeeded;
         try {
-            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile);
+            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile,
+                    false /* downgrade */);
         } catch (IOException ioe) {
             Slog.w(TAG, "IOException reading apk: " + path, ioe);
             return DEX_OPT_FAILED;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 99b74a9..1800db3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2264,7 +2264,7 @@
                             int dexoptNeeded = DexFile.getDexOptNeeded(
                                     lib, dexCodeInstructionSet,
                                     getCompilerFilterForReason(REASON_SHARED_APK),
-                                    false /* newProfile */);
+                                    false /* newProfile */, false /* downgrade */);
                             if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                                 mInstaller.dexopt(lib, Process.SYSTEM_UID, "*",
                                         dexCodeInstructionSet, dexoptNeeded, null,
diff --git a/services/core/java/com/android/server/timezone/FileDescriptorHelper.java b/services/core/java/com/android/server/timezone/FileDescriptorHelper.java
deleted file mode 100644
index c3b1101..0000000
--- a/services/core/java/com/android/server/timezone/FileDescriptorHelper.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.timezone;
-
-import android.os.ParcelFileDescriptor;
-
-import java.io.IOException;
-
-/**
- * An easy-to-mock interface around use of {@link ParcelFileDescriptor} for use by
- * {@link RulesManagerService}.
- */
-interface FileDescriptorHelper {
-
-    byte[] readFully(ParcelFileDescriptor parcelFileDescriptor) throws IOException;
-}
diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
index 3ffbb2d..11928b9 100644
--- a/services/core/java/com/android/server/timezone/IntentHelperImpl.java
+++ b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.timezone;
 
+import com.android.server.EventLogTags;
+
 import android.app.timezone.RulesUpdaterContract;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -24,8 +26,6 @@
 import android.os.PatternMatcher;
 import android.util.Slog;
 
-import java.util.regex.Pattern;
-
 /**
  * The bona fide implementation of {@link IntentHelper}.
  */
@@ -75,6 +75,7 @@
     public void sendTriggerUpdateCheck(CheckToken checkToken) {
         RulesUpdaterContract.sendBroadcast(
                 mContext, mUpdaterAppPackageName, checkToken.toByteArray());
+        EventLogTags.writeTimezoneTriggerCheck(checkToken.toString());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timezone/PackageStatusStorage.java b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
index 31f0e31..cac7f7b 100644
--- a/services/core/java/com/android/server/timezone/PackageStatusStorage.java
+++ b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
@@ -16,73 +16,85 @@
 
 package com.android.server.timezone;
 
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.Xml;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
+import java.io.PrintWriter;
 
 import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_FAILURE;
 import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_SUCCESS;
 import static com.android.server.timezone.PackageStatus.CHECK_STARTED;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 /**
  * Storage logic for accessing/mutating the Android system's persistent state related to time zone
- * update checking. There is expected to be a single instance and all methods synchronized on
- * {@code this} for thread safety.
+ * update checking. There is expected to be a single instance. All non-private methods are thread
+ * safe.
  */
 final class PackageStatusStorage {
 
-    private static final String TAG = "timezone.PackageStatusStorage";
+    private static final String LOG_TAG = "timezone.PackageStatusStorage";
 
-    private static final String DATABASE_NAME = "timezonepackagestatus.db";
-    private static final int DATABASE_VERSION = 1;
-
-    /** The table name. It will have a single row with _id == {@link #SINGLETON_ID} */
-    private static final String TABLE = "status";
-    private static final String COLUMN_ID = "_id";
+    private static final String TAG_PACKAGE_STATUS = "PackageStatus";
 
     /**
-     * Column that stores a monotonically increasing lock ID, used to detect concurrent update
+     * Attribute that stores a monotonically increasing lock ID, used to detect concurrent update
      * issues without on-line locks. Incremented on every write.
      */
-    private static final String COLUMN_OPTIMISTIC_LOCK_ID = "optimistic_lock_id";
+    private static final String ATTRIBUTE_OPTIMISTIC_LOCK_ID = "optimisticLockId";
 
     /**
-     * Column that stores the current "check status" of the time zone update application packages.
+     * Attribute that stores the current "check status" of the time zone update application
+     * packages.
      */
-    private static final String COLUMN_CHECK_STATUS = "check_status";
+    private static final String ATTRIBUTE_CHECK_STATUS = "checkStatus";
 
     /**
-     * Column that stores the version of the time zone rules update application being checked / last
-     * checked.
+     * Attribute that stores the version of the time zone rules update application being checked
+     * / last checked.
      */
-    private static final String COLUMN_UPDATE_APP_VERSION = "update_app_package_version";
+    private static final String ATTRIBUTE_UPDATE_APP_VERSION = "updateAppPackageVersion";
 
     /**
-     * Column that stores the version of the time zone rules data application being checked / last
-     * checked.
+     * Attribute that stores the version of the time zone rules data application being checked
+     * / last checked.
      */
-    private static final String COLUMN_DATA_APP_VERSION = "data_app_package_version";
-
-    /**
-     * The ID of the one row.
-     */
-    private static final int SINGLETON_ID = 1;
+    private static final String ATTRIBUTE_DATA_APP_VERSION = "dataAppPackageVersion";
 
     private static final int UNKNOWN_PACKAGE_VERSION = -1;
 
-    private final DatabaseHelper mDatabaseHelper;
+    private final AtomicFile mPackageStatusFile;
 
-    PackageStatusStorage(Context context) {
-        mDatabaseHelper = new DatabaseHelper(context);
+    PackageStatusStorage(File storageDir) {
+        mPackageStatusFile = new AtomicFile(new File(storageDir, "package-status.xml"));
+        if (!mPackageStatusFile.getBaseFile().exists()) {
+            try {
+                insertInitialPackageStatus();
+            } catch (IOException e) {
+                throw new IllegalStateException(e);
+            }
+        }
     }
 
-    void deleteDatabaseForTests() {
-        SQLiteDatabase.deleteDatabase(mDatabaseHelper.getDatabaseFile());
+    void deleteFileForTests() {
+        synchronized(this) {
+            mPackageStatusFile.delete();
+        }
     }
 
     /**
@@ -92,49 +104,62 @@
     PackageStatus getPackageStatus() {
         synchronized (this) {
             try {
-                return getPackageStatusInternal();
-            } catch (IllegalArgumentException e) {
-                // This means that data exists in the table but it was bad.
-                Slog.e(TAG, "Package status invalid, resetting and retrying", e);
+                return getPackageStatusLocked();
+            } catch (ParseException e) {
+                // This means that data exists in the file but it was bad.
+                Slog.e(LOG_TAG, "Package status invalid, resetting and retrying", e);
 
                 // Reset the storage so it is in a good state again.
-                mDatabaseHelper.recoverFromBadData();
-                return getPackageStatusInternal();
+                recoverFromBadData(e);
+                try {
+                    return getPackageStatusLocked();
+                } catch (ParseException e2) {
+                    throw new IllegalStateException("Recovery from bad file failed", e2);
+                }
             }
         }
     }
 
-    private PackageStatus getPackageStatusInternal() {
-        String[] columns = {
-                COLUMN_CHECK_STATUS, COLUMN_UPDATE_APP_VERSION, COLUMN_DATA_APP_VERSION
-        };
-        Cursor cursor = mDatabaseHelper.getReadableDatabase()
-                .query(TABLE, columns, COLUMN_ID + " = ?",
-                        new String[] { Integer.toString(SINGLETON_ID) },
-                        null /* groupBy */, null /* having */, null /* orderBy */);
-        if (cursor.getCount() != 1) {
-            Slog.e(TAG, "Unable to find package status from package status row. Rows returned: "
-                    + cursor.getCount());
-            return null;
+    @GuardedBy("this")
+    private PackageStatus getPackageStatusLocked() throws ParseException {
+        try (FileInputStream fis = mPackageStatusFile.openRead()) {
+            XmlPullParser parser = parseToPackageStatusTag(fis);
+            Integer checkStatus = getNullableIntAttribute(parser, ATTRIBUTE_CHECK_STATUS);
+            if (checkStatus == null) {
+                return null;
+            }
+            int updateAppVersion = getIntAttribute(parser, ATTRIBUTE_UPDATE_APP_VERSION);
+            int dataAppVersion = getIntAttribute(parser, ATTRIBUTE_DATA_APP_VERSION);
+            return new PackageStatus(checkStatus,
+                    new PackageVersions(updateAppVersion, dataAppVersion));
+        } catch (IOException e) {
+            ParseException e2 = new ParseException("Error reading package status", 0);
+            e2.initCause(e);
+            throw e2;
         }
-        cursor.moveToFirst();
+    }
 
-        // Determine check status.
-        if (cursor.isNull(0)) {
-            // This is normal the first time getPackageStatus() is called, or after
-            // resetCheckState().
-            return null;
+    @GuardedBy("this")
+    private int recoverFromBadData(Exception cause) {
+        mPackageStatusFile.delete();
+        try {
+            return insertInitialPackageStatus();
+        } catch (IOException e) {
+            IllegalStateException fatal = new IllegalStateException(e);
+            fatal.addSuppressed(cause);
+            throw fatal;
         }
-        int checkStatus = cursor.getInt(0);
+    }
 
-        // Determine package version.
-        if (cursor.isNull(1) || cursor.isNull(2)) {
-            Slog.e(TAG, "Package version information unexpectedly null");
-            return null;
-        }
-        PackageVersions packageVersions = new PackageVersions(cursor.getInt(1), cursor.getInt(2));
+    /** Insert the initial data, returning the optimistic lock ID */
+    private int insertInitialPackageStatus() throws IOException {
+        // Doesn't matter what it is, but we avoid the obvious starting value each time the data
+        // is reset to ensure that old tokens are unlikely to work.
+        final int initialOptimisticLockId = (int) System.currentTimeMillis();
 
-        return new PackageStatus(checkStatus, packageVersions);
+        writePackageStatusLocked(null /* status */, initialOptimisticLockId,
+                null /* packageVersions */);
+        return initialOptimisticLockId;
     }
 
     /**
@@ -147,23 +172,29 @@
         }
 
         synchronized (this) {
-            Integer optimisticLockId = getCurrentOptimisticLockId();
-            if (optimisticLockId == null) {
-                Slog.w(TAG, "Unable to find optimistic lock ID from package status row");
+            int optimisticLockId;
+            try {
+                optimisticLockId = getCurrentOptimisticLockId();
+            } catch (ParseException e) {
+                Slog.w(LOG_TAG, "Unable to find optimistic lock ID from package status");
 
                 // Recover.
-                optimisticLockId = mDatabaseHelper.recoverFromBadData();
+                optimisticLockId = recoverFromBadData(e);
             }
 
             int newOptimisticLockId = optimisticLockId + 1;
-            boolean statusRowUpdated = writeStatusRow(
-                    optimisticLockId, newOptimisticLockId, CHECK_STARTED, currentInstalledVersions);
-            if (!statusRowUpdated) {
-                Slog.e(TAG, "Unable to update status to CHECK_STARTED in package status row."
-                        + " synchronization failure?");
-                return null;
+            try {
+                boolean statusUpdated = writePackageStatusWithOptimisticLockCheck(
+                        optimisticLockId, newOptimisticLockId, CHECK_STARTED,
+                        currentInstalledVersions);
+                if (!statusUpdated) {
+                    throw new IllegalStateException("Unable to update status to CHECK_STARTED."
+                            + " synchronization failure?");
+                }
+                return new CheckToken(newOptimisticLockId, currentInstalledVersions);
+            } catch (IOException e) {
+                throw new IllegalStateException(e);
             }
-            return new CheckToken(newOptimisticLockId, currentInstalledVersions);
         }
     }
 
@@ -172,19 +203,25 @@
      */
     void resetCheckState() {
         synchronized(this) {
-            Integer optimisticLockId = getCurrentOptimisticLockId();
-            if (optimisticLockId == null) {
-                Slog.w(TAG, "resetCheckState: Unable to find optimistic lock ID from package"
-                        + " status row");
+            int optimisticLockId;
+            try {
+                optimisticLockId = getCurrentOptimisticLockId();
+            } catch (ParseException e) {
+                Slog.w(LOG_TAG, "resetCheckState: Unable to find optimistic lock ID from package"
+                        + " status");
                 // Attempt to recover the storage state.
-                optimisticLockId = mDatabaseHelper.recoverFromBadData();
+                optimisticLockId = recoverFromBadData(e);
             }
 
             int newOptimisticLockId = optimisticLockId + 1;
-            if (!writeStatusRow(optimisticLockId, newOptimisticLockId,
-                    null /* status */, null /* packageVersions */)) {
-                Slog.e(TAG, "resetCheckState: Unable to reset package status row,"
-                        + " newOptimisticLockId=" + newOptimisticLockId);
+            try {
+                if (!writePackageStatusWithOptimisticLockCheck(optimisticLockId,
+                        newOptimisticLockId, null /* status */, null /* packageVersions */)) {
+                    throw new IllegalStateException("resetCheckState: Unable to reset package"
+                            + " status, newOptimisticLockId=" + newOptimisticLockId);
+                }
+            } catch (IOException e) {
+                throw new IllegalStateException(e);
             }
         }
     }
@@ -199,138 +236,150 @@
             int optimisticLockId = checkToken.mOptimisticLockId;
             int newOptimisticLockId = optimisticLockId + 1;
             int status = succeeded ? CHECK_COMPLETED_SUCCESS : CHECK_COMPLETED_FAILURE;
-            return writeStatusRow(optimisticLockId, newOptimisticLockId,
-                    status, checkToken.mPackageVersions);
-        }
-    }
-
-    // Caller should be synchronized(this)
-    private Integer getCurrentOptimisticLockId() {
-        final String[] columns = { COLUMN_OPTIMISTIC_LOCK_ID };
-        final String querySelection = COLUMN_ID + " = ?";
-        final String[] querySelectionArgs = { Integer.toString(SINGLETON_ID) };
-
-        SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
-        try (Cursor cursor = database.query(TABLE, columns, querySelection, querySelectionArgs,
-                null /* groupBy */, null /* having */, null /* orderBy */)) {
-            if (cursor.getCount() != 1) {
-                Slog.w(TAG, cursor.getCount() + " rows returned, expected exactly one.");
-                return null;
+            try {
+                return writePackageStatusWithOptimisticLockCheck(optimisticLockId,
+                        newOptimisticLockId, status, checkToken.mPackageVersions);
+            } catch (IOException e) {
+                throw new IllegalStateException(e);
             }
-            cursor.moveToFirst();
-            return cursor.getInt(0);
         }
     }
 
-    // Caller should be synchronized(this)
-    private boolean writeStatusRow(int optimisticLockId, int newOptimisticLockId, Integer status,
-            PackageVersions packageVersions) {
+    @GuardedBy("this")
+    private int getCurrentOptimisticLockId() throws ParseException {
+        try (FileInputStream fis = mPackageStatusFile.openRead()) {
+            XmlPullParser parser = parseToPackageStatusTag(fis);
+            return getIntAttribute(parser, ATTRIBUTE_OPTIMISTIC_LOCK_ID);
+        } catch (IOException e) {
+            ParseException e2 = new ParseException("Unable to read file", 0);
+            e2.initCause(e);
+            throw e2;
+        }
+    }
+
+    /** Returns a parser or throws ParseException, never returns null. */
+    private static XmlPullParser parseToPackageStatusTag(FileInputStream fis)
+            throws ParseException {
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, StandardCharsets.UTF_8.name());
+            int type;
+            while ((type = parser.next()) != END_DOCUMENT) {
+                final String tag = parser.getName();
+                if (type == START_TAG && TAG_PACKAGE_STATUS.equals(tag)) {
+                    return parser;
+                }
+            }
+            throw new ParseException("Unable to find " + TAG_PACKAGE_STATUS + " tag", 0);
+        } catch (XmlPullParserException e) {
+            throw new IllegalStateException("Unable to configure parser", e);
+        } catch (IOException e) {
+            ParseException e2 = new ParseException("Error reading XML", 0);
+            e.initCause(e);
+            throw e2;
+        }
+    }
+
+    @GuardedBy("this")
+    private boolean writePackageStatusWithOptimisticLockCheck(int optimisticLockId,
+            int newOptimisticLockId, Integer status, PackageVersions packageVersions)
+            throws IOException {
+
+        int currentOptimisticLockId;
+        try {
+            currentOptimisticLockId = getCurrentOptimisticLockId();
+            if (currentOptimisticLockId != optimisticLockId) {
+                return false;
+            }
+        } catch (ParseException e) {
+            recoverFromBadData(e);
+            return false;
+        }
+
+        writePackageStatusLocked(status, newOptimisticLockId, packageVersions);
+        return true;
+    }
+
+    @GuardedBy("this")
+    private void writePackageStatusLocked(Integer status, int optimisticLockId,
+            PackageVersions packageVersions) throws IOException {
         if ((status == null) != (packageVersions == null)) {
             throw new IllegalArgumentException(
                     "Provide both status and packageVersions, or neither.");
         }
 
-        SQLiteDatabase database = mDatabaseHelper.getWritableDatabase();
-        ContentValues values = new ContentValues();
-        values.put(COLUMN_OPTIMISTIC_LOCK_ID, newOptimisticLockId);
-        if (status == null) {
-            values.putNull(COLUMN_CHECK_STATUS);
-            values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
-            values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
-        } else {
-            values.put(COLUMN_CHECK_STATUS, status);
-            values.put(COLUMN_UPDATE_APP_VERSION, packageVersions.mUpdateAppVersion);
-            values.put(COLUMN_DATA_APP_VERSION, packageVersions.mDataAppVersion);
+        FileOutputStream fos = null;
+        try {
+            fos = mPackageStatusFile.startWrite();
+            XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(fos, StandardCharsets.UTF_8.name());
+            serializer.startDocument(null /* encoding */, true /* standalone */);
+            final String namespace = null;
+            serializer.startTag(namespace, TAG_PACKAGE_STATUS);
+            String statusAttributeValue = status == null ? "" : Integer.toString(status);
+            serializer.attribute(namespace, ATTRIBUTE_CHECK_STATUS, statusAttributeValue);
+            serializer.attribute(namespace, ATTRIBUTE_OPTIMISTIC_LOCK_ID,
+                    Integer.toString(optimisticLockId));
+            int updateAppVersion = status == null
+                    ? UNKNOWN_PACKAGE_VERSION : packageVersions.mUpdateAppVersion;
+            serializer.attribute(namespace, ATTRIBUTE_UPDATE_APP_VERSION,
+                    Integer.toString(updateAppVersion));
+            int dataAppVersion = status == null
+                    ? UNKNOWN_PACKAGE_VERSION : packageVersions.mDataAppVersion;
+            serializer.attribute(namespace, ATTRIBUTE_DATA_APP_VERSION,
+                    Integer.toString(dataAppVersion));
+            serializer.endTag(namespace, TAG_PACKAGE_STATUS);
+            serializer.endDocument();
+            serializer.flush();
+            mPackageStatusFile.finishWrite(fos);
+        } catch (IOException e) {
+            if (fos != null) {
+                mPackageStatusFile.failWrite(fos);
+            }
+            throw e;
         }
 
-        String updateSelection = COLUMN_ID + " = ? AND " + COLUMN_OPTIMISTIC_LOCK_ID + " = ?";
-        String[] updateSelectionArgs = {
-                Integer.toString(SINGLETON_ID), Integer.toString(optimisticLockId)
-        };
-        int count = database.update(TABLE, values, updateSelection, updateSelectionArgs);
-        if (count > 1) {
-            // This has to be because of corruption: there should only ever be one row.
-            Slog.w(TAG, "writeStatusRow: " + count + " rows updated, expected exactly one.");
-            // Reset the table.
-            mDatabaseHelper.recoverFromBadData();
-        }
-
-        // 1 is the success case. 0 rows updated means the row is missing or the optimistic lock ID
-        // was not as expected, this could be because of corruption but is most likely due to an
-        // optimistic lock failure. Callers can decide on a case-by-case basis.
-        return count == 1;
-    }
-
-    /** Only used during tests to force an empty table. */
-    void deleteRowForTests() {
-        mDatabaseHelper.getWritableDatabase().delete(TABLE, null, null);
     }
 
     /** Only used during tests to force a known table state. */
     public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions) {
-        int optimisticLockId = getCurrentOptimisticLockId();
-        writeStatusRow(optimisticLockId, optimisticLockId, checkStatus, packageVersions);
+        synchronized (this) {
+            try {
+                int optimisticLockId = getCurrentOptimisticLockId();
+                writePackageStatusWithOptimisticLockCheck(optimisticLockId, optimisticLockId,
+                        checkStatus, packageVersions);
+            } catch (IOException | ParseException e) {
+                throw new IllegalStateException(e);
+            }
+        }
     }
 
-    static class DatabaseHelper extends SQLiteOpenHelper {
-
-        private final Context mContext;
-
-        public DatabaseHelper(Context context) {
-            super(context, DATABASE_NAME, null, DATABASE_VERSION);
-            mContext = context;
-        }
-
-        @Override
-        public void onCreate(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE " + TABLE + " (" +
-                    "_id INTEGER PRIMARY KEY," +
-                    COLUMN_OPTIMISTIC_LOCK_ID + " INTEGER NOT NULL," +
-                    COLUMN_CHECK_STATUS + " INTEGER," +
-                    COLUMN_UPDATE_APP_VERSION + " INTEGER NOT NULL," +
-                    COLUMN_DATA_APP_VERSION + " INTEGER NOT NULL" +
-                    ");");
-            insertInitialRowState(db);
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
-            // no-op: nothing to upgrade
-        }
-
-        /** Recover the initial data row state, returning the new current optimistic lock ID */
-        int recoverFromBadData() {
-            // Delete the table content.
-            SQLiteDatabase writableDatabase = getWritableDatabase();
-            writableDatabase.delete(TABLE, null /* whereClause */, null /* whereArgs */);
-
-            // Insert the initial content.
-            return insertInitialRowState(writableDatabase);
-        }
-
-        /** Insert the initial data row, returning the optimistic lock ID */
-        private static int insertInitialRowState(SQLiteDatabase db) {
-            // Doesn't matter what it is, but we avoid the obvious starting value each time the row
-            // is reset to ensure that old tokens are unlikely to work.
-           final int initialOptimisticLockId = (int) System.currentTimeMillis();
-
-            // Insert the one row.
-            ContentValues values = new ContentValues();
-            values.put(COLUMN_ID, SINGLETON_ID);
-            values.put(COLUMN_OPTIMISTIC_LOCK_ID, initialOptimisticLockId);
-            values.putNull(COLUMN_CHECK_STATUS);
-            values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
-            values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
-            long id = db.insert(TABLE, null, values);
-            if (id == -1) {
-                Slog.w(TAG, "insertInitialRow: could not insert initial row, id=" + id);
-                return -1;
+    private static Integer getNullableIntAttribute(XmlPullParser parser, String attributeName)
+            throws ParseException {
+        String attributeValue = parser.getAttributeValue(null, attributeName);
+        try {
+            if (attributeValue == null) {
+                throw new ParseException("Attribute " + attributeName + " missing", 0);
+            } else if (attributeValue.isEmpty()) {
+                return null;
             }
-            return initialOptimisticLockId;
+            return Integer.parseInt(attributeValue);
+        } catch (NumberFormatException e) {
+            throw new ParseException(
+                    "Bad integer for attributeName=" + attributeName + ": " + attributeValue, 0);
         }
+    }
 
-        File getDatabaseFile() {
-            return mContext.getDatabasePath(DATABASE_NAME);
+    private static int getIntAttribute(XmlPullParser parser, String attributeName)
+            throws ParseException {
+        Integer value = getNullableIntAttribute(parser, attributeName);
+        if (value == null) {
+            throw new ParseException("Missing attribute " + attributeName, 0);
         }
+        return value;
+    }
+
+    public void dump(PrintWriter printWriter) {
+        printWriter.println("Package status: " + getPackageStatus());
     }
 }
diff --git a/services/core/java/com/android/server/timezone/PackageTracker.java b/services/core/java/com/android/server/timezone/PackageTracker.java
index 8abf7df..24e0fe4 100644
--- a/services/core/java/com/android/server/timezone/PackageTracker.java
+++ b/services/core/java/com/android/server/timezone/PackageTracker.java
@@ -21,9 +21,13 @@
 import android.app.timezone.RulesUpdaterContract;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Environment;
 import android.provider.TimeZoneRulesDataContract;
 import android.util.Slog;
 
+import java.io.File;
+import java.io.PrintWriter;
+
 /**
  * Monitors the installed applications associated with time zone updates. If the app packages are
  * updated it indicates there <em>might</em> be a time zone rules update to apply so a targeted
@@ -81,11 +85,17 @@
     /** Creates the {@link PackageTracker} for normal use. */
     static PackageTracker create(Context context) {
         PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context);
+        // TODO(nfuller): Switch to FileUtils.createDir() when available. http://b/31008728
+        File storageDir = new File(Environment.getDataSystemDirectory(), "timezone");
+        if (!storageDir.exists()) {
+            storageDir.mkdir();
+        }
+
         return new PackageTracker(
                 helperImpl /* clock */,
                 helperImpl /* configHelper */,
                 helperImpl /* packageManagerHelper */,
-                new PackageStatusStorage(context),
+                new PackageStatusStorage(storageDir),
                 new IntentHelperImpl(context));
     }
 
@@ -154,35 +164,27 @@
         }
 
         // Validate the updater application package.
-        // TODO(nfuller) Uncomment or remove the code below. Currently an app stops being a priv-app
-        // after it is replaced by one in data so this check fails. http://b/35995024
-        // try {
-        //     if (!mPackageManagerHelper.isPrivilegedApp(mUpdateAppPackageName)) {
-        //         throw failWithException(
-        //                 "Update app " + mUpdateAppPackageName + " must be a priv-app.", null);
-        //     }
-        // } catch (PackageManager.NameNotFoundException e) {
-        //     throw failWithException("Could not determine update app package details for "
-        //             + mUpdateAppPackageName, e);
-        // }
-        // TODO(nfuller) Consider permission checks. While an updated system app retains permissions
-        // obtained by the system version it's not clear how to check them.
+        try {
+            if (!mPackageManagerHelper.isPrivilegedApp(mUpdateAppPackageName)) {
+                throw logAndThrowRuntimeException(
+                        "Update app " + mUpdateAppPackageName + " must be a priv-app.", null);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw logAndThrowRuntimeException("Could not determine update app package details for "
+                    + mUpdateAppPackageName, e);
+        }
         Slog.d(TAG, "Update app " + mUpdateAppPackageName + " is valid.");
 
         // Validate the data application package.
-        // TODO(nfuller) Uncomment or remove the code below. Currently an app stops being a priv-app
-        // after it is replaced by one in data. http://b/35995024
-        // try {
-        //     if (!mPackageManagerHelper.isPrivilegedApp(mDataAppPackageName)) {
-        //         throw failWithException(
-        //                 "Data app " + mDataAppPackageName + " must be a priv-app.", null);
-        //     }
-        // } catch (PackageManager.NameNotFoundException e) {
-        //     throw failWithException("Could not determine data app package details for "
-        //             + mDataAppPackageName, e);
-        // }
-        // TODO(nfuller) Consider permission checks. While an updated system app retains permissions
-        // obtained by the system version it's not clear how to check them.
+        try {
+            if (!mPackageManagerHelper.isPrivilegedApp(mDataAppPackageName)) {
+                throw logAndThrowRuntimeException(
+                        "Data app " + mDataAppPackageName + " must be a priv-app.", null);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw logAndThrowRuntimeException("Could not determine data app package details for "
+                    + mDataAppPackageName, e);
+        }
         Slog.d(TAG, "Data app " + mDataAppPackageName + " is valid.");
     }
 
@@ -460,7 +462,6 @@
                     + TimeZoneRulesDataContract.AUTHORITY);
             return false;
         }
-        // TODO(nfuller) Add any permissions checks needed.
         return true;
     }
 
@@ -501,4 +502,23 @@
         Slog.wtf(TAG, message, cause);
         throw new RuntimeException(message, cause);
     }
+
+    public void dump(PrintWriter fout) {
+        fout.println("PackageTrackerState: " + toString());
+        mPackageStatusStorage.dump(fout);
+    }
+
+    @Override
+    public String toString() {
+        return "PackageTracker{" +
+                "mTrackingEnabled=" + mTrackingEnabled +
+                ", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' +
+                ", mDataAppPackageName='" + mDataAppPackageName + '\'' +
+                ", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis +
+                ", mFailedCheckRetryCount=" + mFailedCheckRetryCount +
+                ", mLastTriggerTimestamp=" + mLastTriggerTimestamp +
+                ", mCheckTriggered=" + mCheckTriggered +
+                ", mCheckFailureCount=" + mCheckFailureCount +
+                '}';
+    }
 }
diff --git a/services/core/java/com/android/server/timezone/PermissionHelper.java b/services/core/java/com/android/server/timezone/PermissionHelper.java
index ba91c7f..2ec31e2 100644
--- a/services/core/java/com/android/server/timezone/PermissionHelper.java
+++ b/services/core/java/com/android/server/timezone/PermissionHelper.java
@@ -16,10 +16,14 @@
 
 package com.android.server.timezone;
 
+import java.io.PrintWriter;
+
 /**
  * An easy-to-mock interface around permission checks for use by {@link RulesManagerService}.
  */
 public interface PermissionHelper {
 
     void enforceCallerHasPermission(String requiredPermission) throws SecurityException;
+
+    boolean checkDumpPermission(String tag, PrintWriter printWriter);
 }
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 58bdeb9..50f27ed 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -17,11 +17,13 @@
 package com.android.server.timezone;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.EventLogTags;
 import com.android.server.SystemService;
 import com.android.timezone.distro.DistroException;
 import com.android.timezone.distro.DistroVersion;
-import com.android.timezone.distro.TimeZoneDistro;
 import com.android.timezone.distro.StagedDistroOperation;
+import com.android.timezone.distro.TimeZoneDistro;
+import com.android.timezone.distro.installer.TimeZoneDistroInstaller;
 
 import android.app.timezone.Callback;
 import android.app.timezone.DistroFormatVersion;
@@ -36,15 +38,25 @@
 import android.util.Slog;
 
 import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
-import libcore.tzdata.update2.TimeZoneDistroInstaller;
+import libcore.icu.ICU;
+import libcore.util.ZoneInfoDB;
 
-// TODO(nfuller) Add EventLog calls where useful in the system server.
-// TODO(nfuller) Check logging best practices in the system server.
-// TODO(nfuller) Check error handling best practices in the system server.
+import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED;
+import static android.app.timezone.RulesState.DISTRO_STATUS_NONE;
+import static android.app.timezone.RulesState.DISTRO_STATUS_UNKNOWN;
+import static android.app.timezone.RulesState.STAGED_OPERATION_INSTALL;
+import static android.app.timezone.RulesState.STAGED_OPERATION_NONE;
+import static android.app.timezone.RulesState.STAGED_OPERATION_UNINSTALL;
+import static android.app.timezone.RulesState.STAGED_OPERATION_UNKNOWN;
+
 public final class RulesManagerService extends IRulesManager.Stub {
 
     private static final String TAG = "timezone.RulesManagerService";
@@ -83,26 +95,22 @@
     private final PackageTracker mPackageTracker;
     private final Executor mExecutor;
     private final TimeZoneDistroInstaller mInstaller;
-    private final FileDescriptorHelper mFileDescriptorHelper;
 
     private static RulesManagerService create(Context context) {
         RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context);
         return new RulesManagerService(
                 helper /* permissionHelper */,
                 helper /* executor */,
-                helper /* fileDescriptorHelper */,
                 PackageTracker.create(context),
                 new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR));
     }
 
     // A constructor that can be used by tests to supply mocked / faked dependencies.
     RulesManagerService(PermissionHelper permissionHelper,
-            Executor executor,
-            FileDescriptorHelper fileDescriptorHelper, PackageTracker packageTracker,
+            Executor executor, PackageTracker packageTracker,
             TimeZoneDistroInstaller timeZoneDistroInstaller) {
         mPermissionHelper = permissionHelper;
         mExecutor = executor;
-        mFileDescriptorHelper = fileDescriptorHelper;
         mPackageTracker = packageTracker;
         mInstaller = timeZoneDistroInstaller;
     }
@@ -115,6 +123,11 @@
     public RulesState getRulesState() {
         mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
 
+        return getRulesStateInternal();
+    }
+
+    /** Like {@link #getRulesState()} without the permission check. */
+    private RulesState getRulesStateInternal() {
         synchronized(this) {
             String systemRulesVersion;
             try {
@@ -128,18 +141,18 @@
 
             // Determine the staged operation status, if possible.
             DistroRulesVersion stagedDistroRulesVersion = null;
-            int stagedOperationStatus = RulesState.STAGED_OPERATION_UNKNOWN;
+            int stagedOperationStatus = STAGED_OPERATION_UNKNOWN;
             if (!operationInProgress) {
                 StagedDistroOperation stagedDistroOperation;
                 try {
                     stagedDistroOperation = mInstaller.getStagedDistroOperation();
                     if (stagedDistroOperation == null) {
-                        stagedOperationStatus = RulesState.STAGED_OPERATION_NONE;
+                        stagedOperationStatus = STAGED_OPERATION_NONE;
                     } else if (stagedDistroOperation.isUninstall) {
-                        stagedOperationStatus = RulesState.STAGED_OPERATION_UNINSTALL;
+                        stagedOperationStatus = STAGED_OPERATION_UNINSTALL;
                     } else {
                         // Must be an install.
-                        stagedOperationStatus = RulesState.STAGED_OPERATION_INSTALL;
+                        stagedOperationStatus = STAGED_OPERATION_INSTALL;
                         DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion;
                         stagedDistroRulesVersion = new DistroRulesVersion(
                                 stagedDistroVersion.rulesVersion,
@@ -152,16 +165,16 @@
 
             // Determine the installed distro state, if possible.
             DistroVersion installedDistroVersion;
-            int distroStatus = RulesState.DISTRO_STATUS_UNKNOWN;
+            int distroStatus = DISTRO_STATUS_UNKNOWN;
             DistroRulesVersion installedDistroRulesVersion = null;
             if (!operationInProgress) {
                 try {
                     installedDistroVersion = mInstaller.getInstalledDistroVersion();
                     if (installedDistroVersion == null) {
-                        distroStatus = RulesState.DISTRO_STATUS_NONE;
+                        distroStatus = DISTRO_STATUS_NONE;
                         installedDistroRulesVersion = null;
                     } else {
-                        distroStatus = RulesState.DISTRO_STATUS_INSTALLED;
+                        distroStatus = DISTRO_STATUS_INSTALLED;
                         installedDistroRulesVersion = new DistroRulesVersion(
                                 installedDistroVersion.rulesVersion,
                                 installedDistroVersion.revision);
@@ -177,57 +190,84 @@
     }
 
     @Override
-    public int requestInstall(
-            ParcelFileDescriptor timeZoneDistro, byte[] checkTokenBytes, ICallback callback) {
-        mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+    public int requestInstall(ParcelFileDescriptor distroParcelFileDescriptor,
+            byte[] checkTokenBytes, ICallback callback) {
 
-        CheckToken checkToken = null;
-        if (checkTokenBytes != null) {
-            checkToken = createCheckTokenOrThrow(checkTokenBytes);
-        }
-        synchronized (this) {
-            if (timeZoneDistro == null) {
-                throw new NullPointerException("timeZoneDistro == null");
-            }
-            if (callback == null) {
-                throw new NullPointerException("observer == null");
-            }
-            if (mOperationInProgress.get()) {
-                return RulesManager.ERROR_OPERATION_IN_PROGRESS;
-            }
-            mOperationInProgress.set(true);
+        boolean closeParcelFileDescriptorOnExit = true;
+        try {
+            mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
 
-            // Execute the install asynchronously.
-            mExecutor.execute(new InstallRunnable(timeZoneDistro, checkToken, callback));
+            CheckToken checkToken = null;
+            if (checkTokenBytes != null) {
+                checkToken = createCheckTokenOrThrow(checkTokenBytes);
+            }
+            EventLogTags.writeTimezoneRequestInstall(toStringOrNull(checkToken));
 
-            return RulesManager.SUCCESS;
+            synchronized (this) {
+                if (distroParcelFileDescriptor == null) {
+                    throw new NullPointerException("distroParcelFileDescriptor == null");
+                }
+                if (callback == null) {
+                    throw new NullPointerException("observer == null");
+                }
+                if (mOperationInProgress.get()) {
+                    return RulesManager.ERROR_OPERATION_IN_PROGRESS;
+                }
+                mOperationInProgress.set(true);
+
+                // Execute the install asynchronously.
+                mExecutor.execute(
+                        new InstallRunnable(distroParcelFileDescriptor, checkToken, callback));
+
+                // The InstallRunnable now owns the ParcelFileDescriptor, so it will close it after
+                // it executes (and we do not have to).
+                closeParcelFileDescriptorOnExit = false;
+
+                return RulesManager.SUCCESS;
+            }
+        } finally {
+            // We should close() the local ParcelFileDescriptor we were passed if it hasn't been
+            // passed to another thread to handle.
+            if (distroParcelFileDescriptor != null && closeParcelFileDescriptorOnExit) {
+                try {
+                    distroParcelFileDescriptor.close();
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed to close distroParcelFileDescriptor", e);
+                }
+            }
         }
     }
 
     private class InstallRunnable implements Runnable {
 
-        private final ParcelFileDescriptor mTimeZoneDistro;
+        private final ParcelFileDescriptor mDistroParcelFileDescriptor;
         private final CheckToken mCheckToken;
         private final ICallback mCallback;
 
-        InstallRunnable(
-                ParcelFileDescriptor timeZoneDistro, CheckToken checkToken, ICallback callback) {
-            mTimeZoneDistro = timeZoneDistro;
+        InstallRunnable(ParcelFileDescriptor distroParcelFileDescriptor, CheckToken checkToken,
+                ICallback callback) {
+            mDistroParcelFileDescriptor = distroParcelFileDescriptor;
             mCheckToken = checkToken;
             mCallback = callback;
         }
 
         @Override
         public void run() {
+            EventLogTags.writeTimezoneInstallStarted(toStringOrNull(mCheckToken));
+
+            boolean success = false;
             // Adopt the ParcelFileDescriptor into this try-with-resources so it is closed
             // when we are done.
-            boolean success = false;
-            try {
-                byte[] distroBytes =
-                        RulesManagerService.this.mFileDescriptorHelper.readFully(mTimeZoneDistro);
-                TimeZoneDistro distro = new TimeZoneDistro(distroBytes);
+            try (ParcelFileDescriptor pfd = mDistroParcelFileDescriptor) {
+                // The ParcelFileDescriptor owns the underlying FileDescriptor and we'll close
+                // it at the end of the try-with-resources.
+                final boolean isFdOwner = false;
+                InputStream is = new FileInputStream(pfd.getFileDescriptor(), isFdOwner);
+
+                TimeZoneDistro distro = new TimeZoneDistro(is);
                 int installerResult = mInstaller.stageInstallWithErrorCode(distro);
                 int resultCode = mapInstallerResultToApiCode(installerResult);
+                EventLogTags.writeTimezoneInstallComplete(toStringOrNull(mCheckToken), resultCode);
                 sendFinishedStatus(mCallback, resultCode);
 
                 // All the installer failure modes are currently non-recoverable and won't be
@@ -235,6 +275,8 @@
                 success = true;
             } catch (Exception e) {
                 Slog.w(TAG, "Failed to install distro.", e);
+                EventLogTags.writeTimezoneInstallComplete(
+                        toStringOrNull(mCheckToken), Callback.ERROR_UNKNOWN_FAILURE);
                 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
             } finally {
                 // Notify the package tracker that the operation is now complete.
@@ -270,6 +312,7 @@
         if (checkTokenBytes != null) {
             checkToken = createCheckTokenOrThrow(checkTokenBytes);
         }
+        EventLogTags.writeTimezoneRequestUninstall(toStringOrNull(checkToken));
         synchronized(this) {
             if (callback == null) {
                 throw new NullPointerException("callback == null");
@@ -292,13 +335,14 @@
         private final CheckToken mCheckToken;
         private final ICallback mCallback;
 
-        public UninstallRunnable(CheckToken checkToken, ICallback callback) {
+        UninstallRunnable(CheckToken checkToken, ICallback callback) {
             mCheckToken = checkToken;
             mCallback = callback;
         }
 
         @Override
         public void run() {
+            EventLogTags.writeTimezoneUninstallStarted(toStringOrNull(mCheckToken));
             boolean success = false;
             try {
                 success = mInstaller.stageUninstall();
@@ -306,8 +350,12 @@
                 // against SUCCESS. More granular failures may be added in future.
                 int resultCode = success ? Callback.SUCCESS
                         : Callback.ERROR_UNKNOWN_FAILURE;
+                EventLogTags.writeTimezoneUninstallComplete(
+                        toStringOrNull(mCheckToken), resultCode);
                 sendFinishedStatus(mCallback, resultCode);
             } catch (Exception e) {
+                EventLogTags.writeTimezoneUninstallComplete(
+                        toStringOrNull(mCheckToken), Callback.ERROR_UNKNOWN_FAILURE);
                 Slog.w(TAG, "Failed to uninstall distro.", e);
                 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
             } finally {
@@ -334,7 +382,121 @@
         if (checkTokenBytes != null) {
             checkToken = createCheckTokenOrThrow(checkTokenBytes);
         }
+        EventLogTags.writeTimezoneRequestNothing(toStringOrNull(checkToken));
         mPackageTracker.recordCheckResult(checkToken, success);
+        EventLogTags.writeTimezoneNothingComplete(toStringOrNull(checkToken));
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!mPermissionHelper.checkDumpPermission(TAG, pw)) {
+            return;
+        }
+
+        RulesState rulesState = getRulesStateInternal();
+        if (args != null && args.length == 2) {
+            // Formatting options used for automated tests. The format is less free-form than
+            // the -format options, which are intended to be easier to parse.
+            if ("-format_state".equals(args[0]) && args[1] != null) {
+                for (char c : args[1].toCharArray()) {
+                    switch (c) {
+                        case 'p': {
+                            // Report operation in progress
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                value = Boolean.toString(rulesState.isOperationInProgress());
+                            }
+                            pw.println("Operation in progress: " + value);
+                            break;
+                        }
+                        case 's': {
+                            // Report system image rules version
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                value = rulesState.getSystemRulesVersion();
+                            }
+                            pw.println("System rules version: " + value);
+                            break;
+                        }
+                        case 'c': {
+                            // Report current installation state
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                value = distroStatusToString(rulesState.getDistroStatus());
+                            }
+                            pw.println("Current install state: " + value);
+                            break;
+                        }
+                        case 'i': {
+                            // Report currently installed version
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                DistroRulesVersion installedRulesVersion =
+                                        rulesState.getInstalledDistroRulesVersion();
+                                if (installedRulesVersion == null) {
+                                    value = "<None>";
+                                } else {
+                                    value = installedRulesVersion.toDumpString();
+                                }
+                            }
+                            pw.println("Installed rules version: " + value);
+                            break;
+                        }
+                        case 'o': {
+                            // Report staged operation type
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                int stagedOperationType = rulesState.getStagedOperationType();
+                                value = stagedOperationToString(stagedOperationType);
+                            }
+                            pw.println("Staged operation: " + value);
+                            break;
+                        }
+                        case 't': {
+                            // Report staged version (i.e. the one that will be installed next boot
+                            // if the staged operation is an install).
+                            String value = "Unknown";
+                            if (rulesState != null) {
+                                DistroRulesVersion stagedDistroRulesVersion =
+                                        rulesState.getStagedDistroRulesVersion();
+                                if (stagedDistroRulesVersion == null) {
+                                    value = "<None>";
+                                } else {
+                                    value = stagedDistroRulesVersion.toDumpString();
+                                }
+                            }
+                            pw.println("Staged rules version: " + value);
+                            break;
+                        }
+                        case 'a': {
+                            // Report the active rules version (i.e. the rules in use by the current
+                            // process).
+                            pw.println("Active rules version (ICU, libcore): "
+                                    + ICU.getTZDataVersion() + ","
+                                    + ZoneInfoDB.getInstance().getVersion());
+                            break;
+                        }
+                        default: {
+                            pw.println("Unknown option: " + c);
+                        }
+                    }
+                }
+                return;
+            }
+        }
+
+        pw.println("RulesManagerService state: " + toString());
+        pw.println("Active rules version (ICU, libcore): " + ICU.getTZDataVersion() + ","
+                + ZoneInfoDB.getInstance().getVersion());
+        pw.println("Distro state: " + rulesState.toString());
+        mPackageTracker.dump(pw);
+    }
+
+    @Override
+    public String toString() {
+        return "RulesManagerService{" +
+                "mOperationInProgress=" + mOperationInProgress +
+                '}';
     }
 
     private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) {
@@ -347,4 +509,34 @@
         }
         return checkToken;
     }
+
+    private static String distroStatusToString(int distroStatus) {
+        switch(distroStatus) {
+            case DISTRO_STATUS_NONE:
+                return "None";
+            case DISTRO_STATUS_INSTALLED:
+                return "Installed";
+            case DISTRO_STATUS_UNKNOWN:
+            default:
+                return "Unknown";
+        }
+    }
+
+    private static String stagedOperationToString(int stagedOperationType) {
+        switch(stagedOperationType) {
+            case STAGED_OPERATION_NONE:
+                return "None";
+            case STAGED_OPERATION_UNINSTALL:
+                return "Uninstall";
+            case STAGED_OPERATION_INSTALL:
+                return "Install";
+            case STAGED_OPERATION_UNKNOWN:
+            default:
+                return "Unknown";
+        }
+    }
+
+    private static String toStringOrNull(Object obj) {
+        return obj == null ? null : obj.toString();
+    }
 }
diff --git a/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
index 15a571d..0cf61c0 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
@@ -17,18 +17,21 @@
 package com.android.server.timezone;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 import libcore.io.Streams;
 
 /**
  * A single class that implements multiple helper interfaces for use by {@link RulesManagerService}.
  */
-final class RulesManagerServiceHelperImpl
-        implements PermissionHelper, Executor, FileDescriptorHelper {
+final class RulesManagerServiceHelperImpl implements PermissionHelper, Executor {
 
     private final Context mContext;
 
@@ -41,19 +44,21 @@
         mContext.enforceCallingPermission(requiredPermission, null /* message */);
     }
 
-    // TODO Wake lock required?
     @Override
-    public void execute(Runnable runnable) {
-        // TODO Is there a better way?
-        new Thread(runnable).start();
+    public boolean checkDumpPermission(String tag, PrintWriter pw) {
+        // TODO(nfuller): Switch to DumpUtils.checkDumpPermission() when it is available in AOSP.
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump LocationManagerService from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return false;
+        }
+        return true;
     }
 
     @Override
-    public byte[] readFully(ParcelFileDescriptor parcelFileDescriptor) throws IOException {
-        try (ParcelFileDescriptor pfd = parcelFileDescriptor) {
-            // Read bytes
-            FileInputStream in = new FileInputStream(pfd.getFileDescriptor(), false /* isOwner */);
-            return Streams.readFully(in);
-        }
+    public void execute(Runnable runnable) {
+        AsyncTask.execute(runnable);
     }
 }
diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
index 2be69ac..cabce18 100644
--- a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
@@ -17,12 +17,12 @@
 package com.android.server.updates;
 
 import com.android.timezone.distro.TimeZoneDistro;
+import com.android.timezone.distro.installer.TimeZoneDistroInstaller;
 
 import android.util.Slog;
 
 import java.io.File;
 import java.io.IOException;
-import libcore.tzdata.update2.TimeZoneDistroInstaller;
 
 /**
  * An install receiver responsible for installing timezone data updates.
diff --git a/services/core/jni/com_android_server_AlarmManagerService.cpp b/services/core/jni/com_android_server_AlarmManagerService.cpp
index 3a0273d..bcb0b4f 100644
--- a/services/core/jni/com_android_server_AlarmManagerService.cpp
+++ b/services/core/jni/com_android_server_AlarmManagerService.cpp
@@ -17,7 +17,7 @@
 
 #define LOG_TAG "AlarmManagerService"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include <utils/Log.h>
 #include <utils/misc.h>
diff --git a/services/core/jni/com_android_server_ConsumerIrService.cpp b/services/core/jni/com_android_server_ConsumerIrService.cpp
index 7104870..4bad9dd 100644
--- a/services/core/jni/com_android_server_ConsumerIrService.cpp
+++ b/services/core/jni/com_android_server_ConsumerIrService.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "ConsumerIrService"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 
 #include <stdlib.h>
@@ -25,7 +25,7 @@
 #include <utils/Log.h>
 #include <hardware/hardware.h>
 #include <hardware/consumerir.h>
-#include <ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 namespace android {
 
diff --git a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
index 14d50ce..701403b 100644
--- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
+++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "HardwarePropertiesManagerService-JNI"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 
 #include <stdlib.h>
diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
index f94e5d9..97e69fb 100644
--- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp
+++ b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
@@ -15,9 +15,9 @@
  */
 
 #include <android_runtime/AndroidRuntime.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <jni.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 #include <utils/misc.h>
 #include <sys/ioctl.h>
diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp
index 1bd7a59..aef0b25 100644
--- a/services/core/jni/com_android_server_SerialService.cpp
+++ b/services/core/jni/com_android_server_SerialService.cpp
@@ -18,7 +18,7 @@
 #include "utils/Log.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 
 #include <sys/types.h>
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index e46490b..01da4df 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -15,7 +15,7 @@
  */
 
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 #include <sensorservice/SensorService.h>
 
diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp
index 3733a55..f37f870 100644
--- a/services/core/jni/com_android_server_UsbDeviceManager.cpp
+++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp
@@ -18,7 +18,7 @@
 #include "utils/Log.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
 
diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp
index 795f6aa..eab3979 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -18,7 +18,7 @@
 #include "utils/Log.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
 
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp
index e12a016..79d935f 100644
--- a/services/core/jni/com_android_server_UsbMidiDevice.cpp
+++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp
@@ -19,7 +19,7 @@
 #include "utils/Log.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
 
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 03fbd19..d7e7c24 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "VibratorService"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 
 #include <utils/misc.h>
diff --git a/services/core/jni/com_android_server_am_ActivityManagerService.cpp b/services/core/jni/com_android_server_am_ActivityManagerService.cpp
index 50e4502..14abaad 100644
--- a/services/core/jni/com_android_server_am_ActivityManagerService.cpp
+++ b/services/core/jni/com_android_server_am_ActivityManagerService.cpp
@@ -20,8 +20,8 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <jni.h>
 
-#include <ScopedLocalRef.h>
-#include <ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 #include <cutils/log.h>
 #include <utils/misc.h>
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 531f946..1d59762 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -31,8 +31,8 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <jni.h>
 
-#include <ScopedLocalRef.h>
-#include <ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 #include <log/log.h>
 #include <utils/misc.h>
diff --git a/services/core/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp
index 4d85d9a..b6bea11 100644
--- a/services/core/jni/com_android_server_connectivity_Vpn.cpp
+++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp
@@ -38,7 +38,7 @@
 #include "netutils/ifc.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 namespace android
 {
diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
index 4e5c27f..87312f8 100644
--- a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
+++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -18,7 +18,7 @@
 #include <error.h>
 #include <hidl/HidlSupport.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <linux/netfilter/nfnetlink.h>
 #include <linux/netlink.h>
 #include <sys/socket.h>
diff --git a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp
index 39474ec..503f0cf 100644
--- a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp
+++ b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "Fingerprint-JNI"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <inttypes.h>
 
 #include <android_runtime/AndroidRuntime.h>
diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
index a23fbcb..3f6295b 100644
--- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
+++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
@@ -18,8 +18,8 @@
 
 #define LOG_NDEBUG 1
 
-#include <JNIHelp.h>
-#include <ScopedPrimitiveArray.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 
 #include <cstring>
 
diff --git a/services/core/jni/com_android_server_input_InputApplicationHandle.cpp b/services/core/jni/com_android_server_input_InputApplicationHandle.cpp
index e927b60..232b2c2 100644
--- a/services/core/jni/com_android_server_input_InputApplicationHandle.cpp
+++ b/services/core/jni/com_android_server_input_InputApplicationHandle.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "InputApplicationHandle"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/threads.h>
diff --git a/services/core/jni/com_android_server_input_InputApplicationHandle.h b/services/core/jni/com_android_server_input_InputApplicationHandle.h
index e6f25cc..c9af711 100644
--- a/services/core/jni/com_android_server_input_InputApplicationHandle.h
+++ b/services/core/jni/com_android_server_input_InputApplicationHandle.h
@@ -19,7 +19,7 @@
 
 #include <inputflinger/InputApplication.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 
 namespace android {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 6791da9..151c887 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -25,7 +25,7 @@
 #define DEBUG_INPUT_DISPATCHER_POLICY 0
 
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include <atomic>
 #include <cinttypes>
@@ -50,9 +50,9 @@
 #include <android_view_PointerIcon.h>
 #include <android/graphics/GraphicsJNI.h>
 
-#include <ScopedLocalRef.h>
-#include <ScopedPrimitiveArray.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 #include "com_android_server_power_PowerManagerService.h"
 #include "com_android_server_input_InputApplicationHandle.h"
diff --git a/services/core/jni/com_android_server_input_InputWindowHandle.cpp b/services/core/jni/com_android_server_input_InputWindowHandle.cpp
index 197d056..2b2a6fa 100644
--- a/services/core/jni/com_android_server_input_InputWindowHandle.cpp
+++ b/services/core/jni/com_android_server_input_InputWindowHandle.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "InputWindowHandle"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/threads.h>
diff --git a/services/core/jni/com_android_server_input_InputWindowHandle.h b/services/core/jni/com_android_server_input_InputWindowHandle.h
index 8d9e7d7..44d4620 100644
--- a/services/core/jni/com_android_server_input_InputWindowHandle.h
+++ b/services/core/jni/com_android_server_input_InputWindowHandle.h
@@ -19,7 +19,7 @@
 
 #include <inputflinger/InputWindow.h>
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 
 namespace android {
diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp
index bf91fe3..178d15a 100644
--- a/services/core/jni/com_android_server_lights_LightsService.cpp
+++ b/services/core/jni/com_android_server_lights_LightsService.cpp
@@ -17,9 +17,10 @@
 #define LOG_TAG "LightsService"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 
+#include <android-base/chrono_utils.h>
 #include <utils/misc.h>
 #include <utils/Log.h>
 #include <hardware/hardware.h>
@@ -137,8 +138,9 @@
     state.brightnessMode = brightnessMode;
 
     {
-        ALOGD_IF_SLOW(50, "Excessive delay setting light");
+        android::base::Timer t;
         devices->lights[light]->set_light(devices->lights[light], &state);
+        if (t.duration() > 50ms) ALOGD("Excessive delay setting light");
     }
 }
 
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 25e819c..b09cba6 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -18,7 +18,7 @@
 
 #define LOG_NDEBUG 0
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include "hardware/hardware.h"
 #include "hardware/gps_internal.h"
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 048ef76..2593f12 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -21,10 +21,11 @@
 #include "JNIHelp.h"
 #include "jni.h"
 
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 
 #include <limits.h>
 
+#include <android-base/chrono_utils.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 #include <utils/Timers.h>
@@ -125,22 +126,34 @@
 static void nativeSetInteractive(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
     if (gPowerModule) {
         if (enable) {
-            ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(true) while turning screen on");
+            android::base::Timer t;
             gPowerModule->setInteractive(gPowerModule, true);
+            if (t.duration() > 20ms) {
+                ALOGD("Excessive delay in setInteractive(true) while turning screen on");
+            }
         } else {
-            ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(false) while turning screen off");
+            android::base::Timer t;
             gPowerModule->setInteractive(gPowerModule, false);
+            if (t.duration() > 20ms) {
+                ALOGD("Excessive delay in setInteractive(false) while turning screen off");
+            }
         }
     }
 }
 
 static void nativeSetAutoSuspend(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
     if (enable) {
-        ALOGD_IF_SLOW(100, "Excessive delay in autosuspend_enable() while turning screen off");
+        android::base::Timer t;
         autosuspend_enable();
+        if (t.duration() > 100ms) {
+            ALOGD("Excessive delay in autosuspend_enable() while turning screen off");
+        }
     } else {
-        ALOGD_IF_SLOW(100, "Excessive delay in autosuspend_disable() while turning screen on");
+        android::base::Timer t;
         autosuspend_disable();
+        if (t.duration() > 100ms) {
+            ALOGD("Excessive delay in autosuspend_disable() while turning screen on");
+        }
     }
 }
 
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h
index f5fd3d6..a17fd65 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.h
+++ b/services/core/jni/com_android_server_power_PowerManagerService.h
@@ -17,7 +17,7 @@
 #ifndef _ANDROID_SERVER_POWER_MANAGER_SERVICE_H
 #define _ANDROID_SERVER_POWER_MANAGER_SERVICE_H
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 
 #include <powermanager/PowerManager.h>
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index e34a8e8..038ab12 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -21,7 +21,7 @@
 #include "android_os_MessageQueue.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 
 #include <gui/Surface.h>
diff --git a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
index de115c8..980922a 100644
--- a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
+++ b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
@@ -20,7 +20,7 @@
 
 #include "jni.h"
 #include <android_runtime/AndroidRuntime.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <android/keycodes.h>
 
 #include <utils/BitSet.h>
diff --git a/services/core/jni/com_android_server_vr_VrManagerService.cpp b/services/core/jni/com_android_server_vr_VrManagerService.cpp
index 1aba43b..72df5b6 100644
--- a/services/core/jni/com_android_server_vr_VrManagerService.cpp
+++ b/services/core/jni/com_android_server_vr_VrManagerService.cpp
@@ -18,7 +18,7 @@
 
 #include <android_runtime/AndroidRuntime.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 #include <utils/Errors.h>
 #include <utils/Log.h>
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 5d7291a..05f140b 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 #include "utils/Log.h"
 #include "utils/misc.h"
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 99ad00b..ed42988 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -965,8 +965,13 @@
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
             }
 
-            if (!disableNonCoreServices && context.getResources().getBoolean(
-                        R.bool.config_enableUpdateableTimeZoneRules)) {
+            // timezone.RulesManagerService will prevent a device starting up if the chain of trust
+            // required for safe time zone updates might be broken. RuleManagerService cannot do
+            // this check when mOnlyCore == true, so we don't enable the service in this case.
+            final boolean startRulesManagerService =
+                    !mOnlyCore && context.getResources().getBoolean(
+                            R.bool.config_enableUpdateableTimeZoneRules);
+            if (startRulesManagerService) {
                 traceBeginAndSlog("StartTimeZoneRulesManagerService");
                 mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
diff --git a/services/net/OWNERS b/services/net/OWNERS
new file mode 100644
index 0000000..fa26997
--- /dev/null
+++ b/services/net/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+per-file Android.mk = build.master@android.com
+
+ek@google.com
+hugobenichi@google.com
+lorenzo@google.com
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 71201ce..c58b4bd 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -181,6 +181,7 @@
     private static final int ETH_HEADER_LEN = 14;
     private static final int ETH_DEST_ADDR_OFFSET = 0;
     private static final int ETH_ETHERTYPE_OFFSET = 12;
+    private static final int ETH_TYPE_MIN = 0x0600;
     private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
             {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
     // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN.
@@ -236,6 +237,7 @@
     private final IpManager.Callback mIpManagerCallback;
     private final NetworkInterface mNetworkInterface;
     private final IpConnectivityLog mMetricsLog;
+
     @VisibleForTesting
     byte[] mHardwareAddress;
     @VisibleForTesting
@@ -244,6 +246,7 @@
     private long mUniqueCounter;
     @GuardedBy("this")
     private boolean mMulticastFilter;
+    private final boolean mDrop802_3Frames;
     // Our IPv4 address, if we have just one, otherwise null.
     @GuardedBy("this")
     private byte[] mIPv4Address;
@@ -253,11 +256,13 @@
 
     @VisibleForTesting
     ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
-            IpManager.Callback ipManagerCallback, boolean multicastFilter, IpConnectivityLog log) {
+            IpManager.Callback ipManagerCallback, boolean multicastFilter,
+            boolean ieee802_3Filter, IpConnectivityLog log) {
         mApfCapabilities = apfCapabilities;
         mIpManagerCallback = ipManagerCallback;
         mNetworkInterface = networkInterface;
         mMulticastFilter = multicastFilter;
+        mDrop802_3Frames = ieee802_3Filter;
         mMetricsLog = log;
 
         maybeStartFilter();
@@ -879,6 +884,7 @@
     /**
      * Begin generating an APF program to:
      * <ul>
+     * <li>Drop/Pass 802.3 frames (based on policy)
      * <li>Drop ARP requests not for us, if mIPv4Address is set,
      * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC,
      * <li>Drop IPv4 multicast packets, if mMulticastFilter,
@@ -900,6 +906,8 @@
 
         // Here's a basic summary of what the initial program does:
         //
+        // if it's a 802.3 Frame (ethtype < 0x0600):
+        //    drop or pass based on configurations
         // if it's ARP:
         //   insert ARP filter to drop or pass these appropriately
         // if it's IPv4:
@@ -910,9 +918,15 @@
         //   pass
         // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
 
+        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+
+        if (mDrop802_3Frames) {
+            // drop 802.3 frames (ethtype < 0x0600)
+            gen.addJumpIfR0LessThan(ETH_TYPE_MIN, gen.DROP_LABEL);
+        }
+
         // Add ARP filters:
         String skipArpFiltersLabel = "skipArpFilters";
-        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
         gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel);
         generateArpFilterLocked(gen);
         gen.defineLabel(skipArpFiltersLabel);
@@ -1077,7 +1091,7 @@
      */
     public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
             NetworkInterface networkInterface, IpManager.Callback ipManagerCallback,
-            boolean multicastFilter) {
+            boolean multicastFilter, boolean ieee802_3Filter) {
         if (apfCapabilities == null || networkInterface == null) return null;
         if (apfCapabilities.apfVersionSupported == 0) return null;
         if (apfCapabilities.maximumApfProgramSize < 512) {
@@ -1094,7 +1108,7 @@
             return null;
         }
         return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback,
-                multicastFilter, new IpConnectivityLog());
+                multicastFilter, ieee802_3Filter, new IpConnectivityLog());
     }
 
     public synchronized void shutdown() {
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 7b57bbd..6a46b5b 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -23,6 +23,7 @@
 import android.net.DhcpResults;
 import android.net.INetd;
 import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties.ProvisioningChange;
 import android.net.LinkProperties;
@@ -35,6 +36,8 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpManagerEvent;
 import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.NetworkConstants;
+import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.RemoteException;
@@ -48,19 +51,28 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.IState;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.net.NetlinkTracker;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 import java.util.StringJoiner;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 
 /**
@@ -187,7 +199,7 @@
         }
 
         private void log(String msg) {
-            mLocalLog.log(PREFIX + msg);
+            mLog.log(PREFIX + msg);
         }
 
         @Override
@@ -307,6 +319,11 @@
                 return this;
             }
 
+            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+                mConfig.mInitialConfig = initialConfig;
+                return this;
+            }
+
             public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
                 mConfig.mStaticIpConfig = staticConfig;
                 return this;
@@ -341,18 +358,20 @@
         /* package */ boolean mEnableIPv6 = true;
         /* package */ boolean mUsingIpReachabilityMonitor = true;
         /* package */ int mRequestedPreDhcpActionMs;
+        /* package */ InitialConfiguration mInitialConfig;
         /* package */ StaticIpConfiguration mStaticIpConfig;
         /* package */ ApfCapabilities mApfCapabilities;
         /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
         /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
 
-        public ProvisioningConfiguration() {}
+        public ProvisioningConfiguration() {} // used by Builder
 
         public ProvisioningConfiguration(ProvisioningConfiguration other) {
             mEnableIPv4 = other.mEnableIPv4;
             mEnableIPv6 = other.mEnableIPv6;
             mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
             mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+            mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
             mStaticIpConfig = other.mStaticIpConfig;
             mApfCapabilities = other.mApfCapabilities;
             mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
@@ -365,12 +384,124 @@
                     .add("mEnableIPv6: " + mEnableIPv6)
                     .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
                     .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+                    .add("mInitialConfig: " + mInitialConfig)
                     .add("mStaticIpConfig: " + mStaticIpConfig)
                     .add("mApfCapabilities: " + mApfCapabilities)
                     .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
                     .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
                     .toString();
         }
+
+        public boolean isValid() {
+            return (mInitialConfig == null) || mInitialConfig.isValid();
+        }
+    }
+
+    public static class InitialConfiguration {
+        public final Set<LinkAddress> ipAddresses = new HashSet<>();
+        public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+        public final Set<InetAddress> dnsServers = new HashSet<>();
+        public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
+
+        public static InitialConfiguration copy(InitialConfiguration config) {
+            if (config == null) {
+                return null;
+            }
+            InitialConfiguration configCopy = new InitialConfiguration();
+            configCopy.ipAddresses.addAll(config.ipAddresses);
+            configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+            configCopy.dnsServers.addAll(config.dnsServers);
+            return configCopy;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
+                    join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+                    join(", ", dnsServers), gateway);
+        }
+
+        public boolean isValid() {
+            // For every IP address, there must be at least one prefix containing that address.
+            for (LinkAddress addr : ipAddresses) {
+                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+                    return false;
+                }
+            }
+            // For every dns server, there must be at least one prefix containing that address.
+            for (InetAddress addr : dnsServers) {
+                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+                    return false;
+                }
+            }
+            // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+            // (read: compliant with RFC4291#section2.5.4).
+            if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+                return false;
+            }
+            // If directlyConnectedRoutes contains an IPv6 default route
+            // then ipAddresses MUST contain at least one non-ULA GUA.
+            if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+                    && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+                return false;
+            }
+            // The prefix length of routes in directlyConnectedRoutes be within reasonable
+            // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+            if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+                return false;
+            }
+            // There no more than one IPv4 address
+            if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
+                return false;
+            }
+
+            return true;
+        }
+
+        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+            return (addr.getAddress() instanceof Inet4Address)
+                    || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+        }
+
+        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+            return (prefix.getAddress() instanceof Inet4Address)
+                    || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+        }
+
+        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
+        }
+
+        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+            return prefix.getAddress().equals(Inet6Address.ANY);
+        }
+
+        private static boolean isIPv6GUA(LinkAddress addr) {
+            return (addr.getAddress() instanceof Inet6Address) && addr.isGlobalPreferred();
+        }
+
+        private static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+            for (T t : coll) {
+                if (fn.test(t)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+            return !any(coll, not(fn));
+        }
+
+        private static <T> Predicate<T> not(Predicate<T> fn) {
+            return (t) -> !fn.test(t);
+        }
+
+        private static <T> String join(String delimiter, Collection<T> coll) {
+            return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+        }
     }
 
     public static final String DUMP_ARG = "ipmanager";
@@ -414,7 +545,7 @@
     private final WakeupMessage mProvisioningTimeoutAlarm;
     private final WakeupMessage mDhcpActionTimeoutAlarm;
     private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
-    private final LocalLog mLocalLog;
+    private final SharedLog mLog;
     private final LocalLog mConnectivityPacketLog;
     private final MessageHandlingLogger mMsgStateLogger;
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
@@ -435,8 +566,7 @@
     private boolean mMulticastFiltering;
     private long mStartTimeMillis;
 
-    public IpManager(Context context, String ifName, Callback callback)
-                throws IllegalArgumentException {
+    public IpManager(Context context, String ifName, Callback callback) {
         this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
                 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)));
     }
@@ -445,7 +575,7 @@
      * An expanded constructor, useful for dependency injection.
      */
     public IpManager(Context context, String ifName, Callback callback,
-            INetworkManagementService nwService) throws IllegalArgumentException {
+            INetworkManagementService nwService) {
         super(IpManager.class.getSimpleName() + "." + ifName);
         mTag = getName();
 
@@ -455,7 +585,7 @@
         mCallback = new LoggingCallbackWrapper(callback);
         mNwService = nwService;
 
-        mLocalLog = new LocalLog(MAX_LOG_RECORDS);
+        mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
         mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
         mMsgStateLogger = new MessageHandlingLogger();
 
@@ -500,7 +630,7 @@
 
             private void logMsg(String msg) {
                 Log.d(mTag, msg);
-                getHandler().post(() -> { mLocalLog.log("OBSERVED " + msg); });
+                getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
             }
         };
 
@@ -508,7 +638,7 @@
         mLinkProperties.setInterfaceName(mInterfaceName);
 
         mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
-                () -> { mLocalLog.log("OBSERVED AvoidBadWifi changed"); });
+                () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
 
         mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
                 mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
@@ -562,6 +692,11 @@
     }
 
     public void startProvisioning(ProvisioningConfiguration req) {
+        if (!req.isValid()) {
+            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+            return;
+        }
+
         getNetworkInterface();
 
         mCallback.setNeighborDiscoveryOffload(true);
@@ -658,7 +793,7 @@
         pw.println();
         pw.println(mTag + " StateMachine dump:");
         pw.increaseIndent();
-        mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
+        mLog.dump(fd, pw, args);
         pw.decreaseIndent();
 
         pw.println();
@@ -693,7 +828,7 @@
                 msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
 
         final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
-        mLocalLog.log(richerLogLine);
+        mLog.log(richerLogLine);
         if (VDBG) {
             Log.d(mTag, richerLogLine);
         }
@@ -714,11 +849,10 @@
         return shouldLog;
     }
 
-    // TODO: Migrate all Log.e(...) to logError(...).
     private void logError(String fmt, Object... args) {
         final String msg = "ERROR " + String.format(fmt, args);
         Log.e(mTag, msg);
-        mLocalLog.log(msg);
+        mLog.log(msg);
     }
 
     private void getNetworkInterface() {
@@ -1035,7 +1169,7 @@
     }
 
     private void doImmediateProvisioningFailure(int failureType) {
-        if (DBG) { Log.e(mTag, "onProvisioningFailure(): " + failureType); }
+        logError("onProvisioningFailure(): %s", failureType);
         recordMetric(failureType);
         mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
     }
@@ -1064,7 +1198,7 @@
             mNwService.setIPv6AddrGenMode(mInterfaceName, mConfiguration.mIPv6AddrGenMode);
         } catch (ServiceSpecificException e) {
             if (e.errorCode != OsConstants.EOPNOTSUPP) {
-                throw e;
+                logError("Unable to set IPv6 addrgen mode: %s", e);
             }
         }
     }
@@ -1089,6 +1223,7 @@
             mIpReachabilityMonitor = new IpReachabilityMonitor(
                     mContext,
                     mInterfaceName,
+                    mLog,
                     new IpReachabilityMonitor.Callback() {
                         @Override
                         public void notifyLost(InetAddress ip, String logMsg) {
@@ -1170,7 +1305,7 @@
 
                 case DhcpClient.CMD_ON_QUIT:
                     // Everything is already stopped.
-                    Log.e(mTag, "Unexpected CMD_ON_QUIT (already stopped).");
+                    logError("Unexpected CMD_ON_QUIT (already stopped).");
                     break;
 
                 default:
@@ -1284,8 +1419,12 @@
 
         @Override
         public void enter() {
+            // Get the Configuration for ApfFilter from Context
+            boolean filter802_3Frames =
+                    mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
+
             mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
-                    mCallback, mMulticastFiltering);
+                    mCallback, mMulticastFiltering, filter802_3Frames);
             // TODO: investigate the effects of any multicast filtering racing/interfering with the
             // rest of this IP configuration startup.
             if (mApfFilter == null) {
@@ -1376,7 +1515,7 @@
                     break;
 
                 case CMD_START:
-                    Log.e(mTag, "ALERT: START received in StartedState. Please fix caller.");
+                    logError("ALERT: START received in StartedState. Please fix caller.");
                     break;
 
                 case CMD_CONFIRM:
@@ -1475,13 +1614,13 @@
                             handleIPv4Failure();
                             break;
                         default:
-                            Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
+                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
                     }
                     break;
 
                 case DhcpClient.CMD_ON_QUIT:
                     // DHCPv4 quit early for some reason.
-                    Log.e(mTag, "Unexpected CMD_ON_QUIT.");
+                    logError("Unexpected CMD_ON_QUIT.");
                     mDhcpClient = null;
                     break;
 
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index a004dbb..e4121b6 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -16,8 +16,6 @@
 
 package android.net.ip;
 
-import com.android.internal.annotations.GuardedBy;
-
 import android.content.Context;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -31,17 +29,22 @@
 import android.net.netlink.NetlinkMessage;
 import android.net.netlink.NetlinkSocket;
 import android.net.netlink.RtNetlinkNeighborMessage;
-import android.net.netlink.StructNdaCacheInfo;
 import android.net.netlink.StructNdMsg;
+import android.net.netlink.StructNdaCacheInfo;
 import android.net.netlink.StructNlMsgHdr;
 import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.SharedLog;
 import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.OsConstants;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.InterruptedIOException;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -146,11 +149,32 @@
         public void notifyLost(InetAddress ip, String logMsg);
     }
 
+    /**
+     * Encapsulates IpReachabilityMonitor depencencies on systems that hinder unit testing.
+     * TODO: consider also wrapping MultinetworkPolicyTracker in this interface.
+     */
+    interface Dependencies {
+        void acquireWakeLock(long durationMs);
+
+        static Dependencies makeDefault(Context context, String iface) {
+            final String lockName = TAG + "." + iface;
+            final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+            final WakeLock lock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
+
+            return new Dependencies() {
+                public void acquireWakeLock(long durationMs) {
+                    lock.acquire(durationMs);
+                }
+            };
+        }
+    }
+
     private final Object mLock = new Object();
-    private final PowerManager.WakeLock mWakeLock;
     private final String mInterfaceName;
     private final int mInterfaceIndex;
+    private final SharedLog mLog;
     private final Callback mCallback;
+    private final Dependencies mDependencies;
     private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
     private final NetlinkSocketObserver mNetlinkSocketObserver;
     private final Thread mObserverThread;
@@ -222,24 +246,25 @@
         return errno;
     }
 
-    public IpReachabilityMonitor(Context context, String ifName, Callback callback) {
-        this(context, ifName, callback, null);
+    public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback) {
+        this(context, ifName, log, callback, null);
     }
 
-    public IpReachabilityMonitor(Context context, String ifName, Callback callback,
-            MultinetworkPolicyTracker tracker) throws IllegalArgumentException {
+    public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback,
+            MultinetworkPolicyTracker tracker) {
+        this(ifName, getInterfaceIndex(ifName), log, callback, tracker,
+                Dependencies.makeDefault(context, ifName));
+    }
+
+    @VisibleForTesting
+    IpReachabilityMonitor(String ifName, int ifIndex, SharedLog log, Callback callback,
+            MultinetworkPolicyTracker tracker, Dependencies dependencies) {
         mInterfaceName = ifName;
-        int ifIndex = -1;
-        try {
-            NetworkInterface netIf = NetworkInterface.getByName(ifName);
-            mInterfaceIndex = netIf.getIndex();
-        } catch (SocketException | NullPointerException e) {
-            throw new IllegalArgumentException("invalid interface '" + ifName + "': ", e);
-        }
-        mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
-                PowerManager.PARTIAL_WAKE_LOCK, TAG + "." + mInterfaceName);
+        mLog = log.forSubComponent(TAG);
         mCallback = callback;
         mMultinetworkPolicyTracker = tracker;
+        mInterfaceIndex = ifIndex;
+        mDependencies = dependencies;
         mNetlinkSocketObserver = new NetlinkSocketObserver();
         mObserverThread = new Thread(mNetlinkSocketObserver);
         mObserverThread.start();
@@ -402,7 +427,7 @@
             // The wakelock we use is (by default) refcounted, and this version
             // of acquire(timeout) queues a release message to keep acquisitions
             // and releases balanced.
-            mWakeLock.acquire(getProbeWakeLockDuration());
+            mDependencies.acquireWakeLock(getProbeWakeLockDuration());
         }
 
         for (InetAddress target : ipProbeList) {
@@ -410,6 +435,8 @@
                 break;
             }
             final int returnValue = probeNeighbor(mInterfaceIndex, target);
+            mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
+                     target.getHostAddress(), returnValue));
             logEvent(IpReachabilityEvent.PROBE, returnValue);
         }
         mLastProbeTimeMs = SystemClock.elapsedRealtime();
@@ -431,6 +458,19 @@
         return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
     }
 
+    private static int getInterfaceIndex(String ifname) {
+        final NetworkInterface iface;
+        try {
+            iface = NetworkInterface.getByName(ifname);
+        } catch (SocketException e) {
+            throw new IllegalArgumentException("invalid interface '" + ifname + "': ", e);
+        }
+        if (iface == null) {
+            throw new IllegalArgumentException("NetworkInterface was null for " + ifname);
+        }
+        return iface.getIndex();
+    }
+
     private void logEvent(int probeType, int errorCode) {
         int eventType = probeType | (errorCode & 0xff);
         mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType));
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index a012e0c..9b3bc3f 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -102,6 +102,7 @@
     public static final int IPV6_ADDR_LEN = 16;
     public static final int IPV6_MIN_MTU = 1280;
     public static final int RFC7421_PREFIX_LENGTH = 64;
+    public static final int RFC6177_MIN_PREFIX_LENGTH = 48;
 
     /**
      * ICMPv6 constants.
diff --git a/services/net/java/android/net/util/PrefixUtils.java b/services/net/java/android/net/util/PrefixUtils.java
new file mode 100644
index 0000000..962aab4
--- /dev/null
+++ b/services/net/java/android/net/util/PrefixUtils.java
@@ -0,0 +1,74 @@
+/*
+ * 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.util;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * @hide
+ */
+public class PrefixUtils {
+    private static final IpPrefix[] MIN_NON_FORWARDABLE_PREFIXES = {
+            pfx("127.0.0.0/8"),     // IPv4 loopback
+            pfx("169.254.0.0/16"),  // IPv4 link-local, RFC3927#section-8
+            pfx("::/3"),
+            pfx("fe80::/64"),       // IPv6 link-local
+            pfx("fc00::/7"),        // IPv6 ULA
+            pfx("ff02::/8"),        // IPv6 link-local multicast
+    };
+
+    public static final IpPrefix DEFAULT_WIFI_P2P_PREFIX = pfx("192.168.49.0/24");
+
+    public static Set<IpPrefix> getNonForwardablePrefixes() {
+        final HashSet<IpPrefix> prefixes = new HashSet<>();
+        addNonForwardablePrefixes(prefixes);
+        return prefixes;
+    }
+
+    public static void addNonForwardablePrefixes(Set<IpPrefix> prefixes) {
+        Collections.addAll(prefixes, MIN_NON_FORWARDABLE_PREFIXES);
+    }
+
+    public static Set<IpPrefix> localPrefixesFrom(LinkProperties lp) {
+        final HashSet<IpPrefix> localPrefixes = new HashSet<>();
+        if (lp == null) return localPrefixes;
+
+        for (LinkAddress addr : lp.getAllLinkAddresses()) {
+            if (addr.getAddress().isLinkLocalAddress()) continue;
+            localPrefixes.add(asIpPrefix(addr));
+        }
+        // TODO: Add directly-connected routes as well (ones from which we did
+        // not also form a LinkAddress)?
+
+        return localPrefixes;
+    }
+
+    public static IpPrefix asIpPrefix(LinkAddress addr) {
+        return new IpPrefix(addr.getAddress(), addr.getPrefixLength());
+    }
+
+    private static IpPrefix pfx(String prefixStr) {
+        return new IpPrefix(prefixStr);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
index e085270..b57cac0 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
@@ -21,10 +21,13 @@
 import org.junit.Test;
 
 import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 import static junit.framework.Assert.assertTrue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -40,15 +43,16 @@
     @Before
     public void setUp() throws Exception {
         Context context = InstrumentationRegistry.getContext();
+        File dataDir = context.getFilesDir();
 
         // Using the instrumentation context means the database is created in a test app-specific
         // directory.
-        mPackageStatusStorage = new PackageStatusStorage(context);
+        mPackageStatusStorage = new PackageStatusStorage(dataDir);
     }
 
     @After
     public void tearDown() throws Exception {
-        mPackageStatusStorage.deleteDatabaseForTests();
+        mPackageStatusStorage.deleteFileForTests();
     }
 
     @Test
@@ -90,7 +94,7 @@
     }
 
     @Test
-    public void generateCheckToken_missingRowBehavior() {
+    public void generateCheckToken_missingFileBehavior() {
         // Assert initial state.
         assertNull(mPackageStatusStorage.getPackageStatus());
 
@@ -100,15 +104,15 @@
         // There should now be state.
         assertNotNull(mPackageStatusStorage.getPackageStatus());
 
-        // Corrupt the table by removing the one row.
-        mPackageStatusStorage.deleteRowForTests();
+        // Corrupt the data by removing the file.
+        mPackageStatusStorage.deleteFileForTests();
 
         // Check that generateCheckToken recovers.
         assertNotNull(mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS));
     }
 
     @Test
-    public void getPackageStatus_missingRowBehavior() {
+    public void getPackageStatus_missingFileBehavior() {
         // Assert initial state.
         assertNull(mPackageStatusStorage.getPackageStatus());
 
@@ -118,14 +122,14 @@
         // There should now be a state.
         assertNotNull(mPackageStatusStorage.getPackageStatus());
 
-        // Corrupt the table by removing the one row.
-        mPackageStatusStorage.deleteRowForTests();
+        // Corrupt the data by removing the file.
+        mPackageStatusStorage.deleteFileForTests();
 
         assertNull(mPackageStatusStorage.getPackageStatus());
     }
 
     @Test
-    public void markChecked_missingRowBehavior() {
+    public void markChecked_missingFileBehavior() {
         // Assert initial state.
         CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
         assertNotNull(token1);
@@ -133,10 +137,10 @@
         // There should now be a state.
         assertNotNull(mPackageStatusStorage.getPackageStatus());
 
-        // Corrupt the table by removing the one row.
-        mPackageStatusStorage.deleteRowForTests();
+        // Corrupt the data by removing the file.
+        mPackageStatusStorage.deleteFileForTests();
 
-        // The missing row should mean token1 is now considered invalid, so we should get a false.
+        // The missing file should mean token1 is now considered invalid, so we should get a false.
         assertFalse(mPackageStatusStorage.markChecked(token1, true /* succeeded */));
 
         // The storage should have recovered and we should be able to carry on like before.
@@ -226,4 +230,27 @@
         assertFalse(writeOk2);
         assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
     }
+
+    @Test
+    public void dump() {
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        // Dump initial state.
+        mPackageStatusStorage.dump(printWriter);
+
+        // No crash and it does something.
+        assertFalse(stringWriter.toString().isEmpty());
+
+        // Reset
+        stringWriter.getBuffer().setLength(0);
+        assertTrue(stringWriter.toString().isEmpty());
+
+        // Store something.
+        mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+        mPackageStatusStorage.dump(printWriter);
+
+        assertFalse(stringWriter.toString().isEmpty());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
index 45b0af3..38142d3 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
@@ -30,6 +30,9 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -71,7 +74,7 @@
 
         // Using the instrumentation context means the database is created in a test app-specific
         // directory. We can use the real thing for this test.
-        mPackageStatusStorage = new PackageStatusStorage(context);
+        mPackageStatusStorage = new PackageStatusStorage(context.getFilesDir());
 
         // For other interactions with the Android framework we create a fake object.
         mFakeIntentHelper = new FakeIntentHelper();
@@ -88,7 +91,7 @@
     @After
     public void tearDown() throws Exception {
         if (mPackageStatusStorage != null) {
-            mPackageStatusStorage.deleteDatabaseForTests();
+            mPackageStatusStorage.deleteFileForTests();
         }
     }
 
@@ -195,26 +198,25 @@
         mFakeIntentHelper.assertReliabilityTriggeringDisabled();
     }
 
-    // TODO(nfuller): Uncomment or delete when it's clear what will happen with http://b/35995024
-    // @Test
-    // public void trackingEnabled_updateAppNotPrivileged() throws Exception {
-    //     // Set up device configuration.
-    //     configureTrackingEnabled();
-    //     configureReliabilityConfigSettingsOk();
-    //     configureUpdateAppPackageNotPrivileged(UPDATE_APP_PACKAGE_NAME);
-    //     configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
-    //
-    //     try {
-    //         // Initialize the tracker.
-    //         mPackageTracker.start();
-    //         fail();
-    //     } catch (RuntimeException expected) {}
-    //
-    //     mFakeIntentHelper.assertNotInitialized();
-    //
-    //     // Check reliability triggering state.
-    //     mFakeIntentHelper.assertReliabilityTriggeringDisabled();
-    // }
+    @Test
+    public void trackingEnabled_updateAppNotPrivileged() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureUpdateAppPackageNotPrivileged(UPDATE_APP_PACKAGE_NAME);
+        configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
+
+        try {
+            // Initialize the tracker.
+            mPackageTracker.start();
+            fail();
+        } catch (RuntimeException expected) {}
+
+        mFakeIntentHelper.assertNotInitialized();
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+    }
 
     @Test
     public void trackingEnabled_dataAppConfigMissing() throws Exception {
@@ -236,26 +238,25 @@
         mFakeIntentHelper.assertReliabilityTriggeringDisabled();
     }
 
-    // TODO(nfuller): Uncomment or delete when it's clear what will happen with http://b/35995024
-    // @Test
-    // public void trackingEnabled_dataAppNotPrivileged() throws Exception {
-    //     // Set up device configuration.
-    //     configureTrackingEnabled();
-    //     configureReliabilityConfigSettingsOk();
-    //     configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
-    //     configureDataAppPackageNotPrivileged(DATA_APP_PACKAGE_NAME);
-    //
-    //     try {
-    //         // Initialize the tracker.
-    //         mPackageTracker.start();
-    //         fail();
-    //     } catch (RuntimeException expected) {}
-    //
-    //     mFakeIntentHelper.assertNotInitialized();
-    //
-    //     // Check reliability triggering state.
-    //     mFakeIntentHelper.assertReliabilityTriggeringDisabled();
-    // }
+    @Test
+    public void trackingEnabled_dataAppNotPrivileged() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
+        configureDataAppPackageNotPrivileged(DATA_APP_PACKAGE_NAME);
+
+        try {
+            // Initialize the tracker.
+            mPackageTracker.start();
+            fail();
+        } catch (RuntimeException expected) {}
+
+        mFakeIntentHelper.assertNotInitialized();
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+     }
 
     @Test
     public void trackingEnabled_packageUpdate_badUpdateAppManifestEntry() throws Exception {
@@ -1174,6 +1175,16 @@
         assertFalse(token1.equals(token2));
     }
 
+    @Test
+    public void dump() {
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        mPackageTracker.dump(printWriter);
+
+        assertFalse(stringWriter.toString().isEmpty());
+    }
+
     private void simulatePackageInstallation(PackageVersions packageVersions) throws Exception {
         configureApplicationsValidManifests(packageVersions);
 
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
index 86116a9..2887e3b 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -17,8 +17,9 @@
 package com.android.server.timezone;
 
 import com.android.timezone.distro.DistroVersion;
-import com.android.timezone.distro.TimeZoneDistro;
 import com.android.timezone.distro.StagedDistroOperation;
+import com.android.timezone.distro.TimeZoneDistro;
+import com.android.timezone.distro.installer.TimeZoneDistroInstaller;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -30,10 +31,15 @@
 import android.app.timezone.RulesState;
 import android.os.ParcelFileDescriptor;
 
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 import javax.annotation.Nullable;
-import libcore.tzdata.update2.TimeZoneDistroInstaller;
+
+import libcore.io.IoUtils;
 
 import static com.android.server.timezone.RulesManagerService.REQUIRED_UPDATER_PERMISSION;
 import static org.junit.Assert.assertEquals;
@@ -42,7 +48,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -50,6 +56,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -61,7 +68,6 @@
 
     private FakeExecutor mFakeExecutor;
     private PermissionHelper mMockPermissionHelper;
-    private FileDescriptorHelper mMockFileDescriptorHelper;
     private PackageTracker mMockPackageTracker;
     private TimeZoneDistroInstaller mMockTimeZoneDistroInstaller;
 
@@ -69,7 +75,6 @@
     public void setUp() {
         mFakeExecutor = new FakeExecutor();
 
-        mMockFileDescriptorHelper = mock(FileDescriptorHelper.class);
         mMockPackageTracker = mock(PackageTracker.class);
         mMockPermissionHelper = mock(PermissionHelper.class);
         mMockTimeZoneDistroInstaller = mock(TimeZoneDistroInstaller.class);
@@ -77,7 +82,6 @@
         mRulesManagerService = new RulesManagerService(
                 mMockPermissionHelper,
                 mFakeExecutor,
-                mMockFileDescriptorHelper,
                 mMockPackageTracker,
                 mMockTimeZoneDistroInstaller);
     }
@@ -273,9 +277,8 @@
                 revision);
         configureInstalledDistroVersion(installedDistroVersion);
 
-        byte[] expectedContent = createArbitraryBytes(1000);
-        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
-        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+        ParcelFileDescriptor parcelFileDescriptor =
+                createParcelFileDescriptor(createArbitraryBytes(1000));
 
         // Start an async operation so there is one in progress. The mFakeExecutor won't actually
         // execute it.
@@ -284,36 +287,41 @@
 
         mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
 
+        // Request the rules state while the async operation is "happening".
+        RulesState actualRulesState = mRulesManagerService.getRulesState();
         RulesState expectedRuleState = new RulesState(
                 systemRulesVersion, RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
                 true /* operationInProgress */,
                 RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
                 RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
-        assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+        assertEquals(expectedRuleState, actualRulesState);
     }
 
     @Test
     public void requestInstall_operationInProgress() throws Exception {
         configureCallerHasPermission();
 
-        byte[] expectedContent = createArbitraryBytes(1000);
-        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
-        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+        ParcelFileDescriptor parcelFileDescriptor1 =
+                createParcelFileDescriptor(createArbitraryBytes(1000));
 
         byte[] tokenBytes = createArbitraryTokenBytes();
         ICallback callback = new StubbedCallback();
 
         // First request should succeed.
         assertEquals(RulesManager.SUCCESS,
-                mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+                mRulesManagerService.requestInstall(parcelFileDescriptor1, tokenBytes, callback));
 
         // Something async should be enqueued. Clear it but do not execute it so we can detect the
         // second request does nothing.
         mFakeExecutor.getAndResetLastCommand();
 
         // Second request should fail.
+        ParcelFileDescriptor parcelFileDescriptor2 =
+                createParcelFileDescriptor(createArbitraryBytes(1000));
         assertEquals(RulesManager.ERROR_OPERATION_IN_PROGRESS,
-                mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+                mRulesManagerService.requestInstall(parcelFileDescriptor2, tokenBytes, callback));
+
+        assertClosed(parcelFileDescriptor2);
 
         // Assert nothing async was enqueued.
         mFakeExecutor.assertNothingQueued();
@@ -325,9 +333,8 @@
     public void requestInstall_badToken() throws Exception {
         configureCallerHasPermission();
 
-        byte[] expectedContent = createArbitraryBytes(1000);
-        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
-        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+        ParcelFileDescriptor parcelFileDescriptor =
+                createParcelFileDescriptor(createArbitraryBytes(1000));
 
         byte[] badTokenBytes = new byte[2];
         ICallback callback = new StubbedCallback();
@@ -338,6 +345,8 @@
         } catch (IllegalArgumentException expected) {
         }
 
+        assertClosed(parcelFileDescriptor);
+
         // Assert nothing async was enqueued.
         mFakeExecutor.assertNothingQueued();
         verifyNoInstallerCallsMade();
@@ -367,7 +376,8 @@
     public void requestInstall_nullCallback() throws Exception {
         configureCallerHasPermission();
 
-        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+        ParcelFileDescriptor parcelFileDescriptor =
+                createParcelFileDescriptor(createArbitraryBytes(1000));
         byte[] tokenBytes = createArbitraryTokenBytes();
         ICallback callback = null;
 
@@ -376,6 +386,8 @@
             fail();
         } catch (NullPointerException expected) {}
 
+        assertClosed(parcelFileDescriptor);
+
         // Assert nothing async was enqueued.
         mFakeExecutor.assertNothingQueued();
         verifyNoInstallerCallsMade();
@@ -386,9 +398,8 @@
     public void requestInstall_asyncSuccess() throws Exception {
         configureCallerHasPermission();
 
-        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
-        byte[] expectedContent = createArbitraryBytes(1000);
-        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+        ParcelFileDescriptor parcelFileDescriptor =
+                createParcelFileDescriptor(createArbitraryBytes(1000));
 
         CheckToken token = createArbitraryToken();
         byte[] tokenBytes = token.toByteArray();
@@ -404,16 +415,16 @@
         verifyNoInstallerCallsMade();
         verifyNoPackageTrackerCallsMade();
 
-        TimeZoneDistro expectedDistro = new TimeZoneDistro(expectedContent);
-
         // Set up the installer.
-        configureStageInstallExpectation(expectedDistro, TimeZoneDistroInstaller.INSTALL_SUCCESS);
+        configureStageInstallExpectation(TimeZoneDistroInstaller.INSTALL_SUCCESS);
 
         // Simulate the async execution.
         mFakeExecutor.simulateAsyncExecutionOfLastCommand();
 
+        assertClosed(parcelFileDescriptor);
+
         // Verify the expected calls were made to other components.
-        verifyStageInstallCalled(expectedDistro);
+        verifyStageInstallCalled();
         verifyPackageTrackerCalled(token, true /* success */);
 
         // Check the callback was called.
@@ -424,9 +435,8 @@
     public void requestInstall_nullTokenBytes() throws Exception {
         configureCallerHasPermission();
 
-        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
-        byte[] expectedContent = createArbitraryBytes(1000);
-        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+        ParcelFileDescriptor parcelFileDescriptor =
+                createParcelFileDescriptor(createArbitraryBytes(1000));
 
         TestCallback callback = new TestCallback();
 
@@ -439,16 +449,16 @@
         verifyNoInstallerCallsMade();
         callback.assertNoResultReceived();
 
-        TimeZoneDistro expectedDistro = new TimeZoneDistro(expectedContent);
-
         // Set up the installer.
-        configureStageInstallExpectation(expectedDistro, TimeZoneDistroInstaller.INSTALL_SUCCESS);
+        configureStageInstallExpectation(TimeZoneDistroInstaller.INSTALL_SUCCESS);
 
         // Simulate the async execution.
         mFakeExecutor.simulateAsyncExecutionOfLastCommand();
 
+        assertClosed(parcelFileDescriptor);
+
         // Verify the expected calls were made to other components.
-        verifyStageInstallCalled(expectedDistro);
+        verifyStageInstallCalled();
         verifyPackageTrackerCalled(null /* expectedToken */, true /* success */);
 
         // Check the callback was received.
@@ -459,9 +469,8 @@
     public void requestInstall_asyncInstallFail() throws Exception {
         configureCallerHasPermission();
 
-        byte[] expectedContent = createArbitraryBytes(1000);
-        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
-        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+        ParcelFileDescriptor parcelFileDescriptor =
+                createParcelFileDescriptor(createArbitraryBytes(1000));
 
         CheckToken token = createArbitraryToken();
         byte[] tokenBytes = token.toByteArray();
@@ -476,17 +485,16 @@
         verifyNoInstallerCallsMade();
         callback.assertNoResultReceived();
 
-        TimeZoneDistro expectedDistro = new TimeZoneDistro(expectedContent);
-
         // Set up the installer.
-        configureStageInstallExpectation(
-                expectedDistro, TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR);
+        configureStageInstallExpectation(TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR);
 
         // Simulate the async execution.
         mFakeExecutor.simulateAsyncExecutionOfLastCommand();
 
+        assertClosed(parcelFileDescriptor);
+
         // Verify the expected calls were made to other components.
-        verifyStageInstallCalled(expectedDistro);
+        verifyStageInstallCalled();
 
         // Validation failure is treated like a successful check: repeating it won't improve things.
         boolean expectedSuccess = true;
@@ -497,38 +505,6 @@
     }
 
     @Test
-    public void requestInstall_asyncParcelFileDescriptorReadFail() throws Exception {
-        configureCallerHasPermission();
-
-        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
-        configureParcelFileDescriptorReadFailure(parcelFileDescriptor);
-
-        CheckToken token = createArbitraryToken();
-        byte[] tokenBytes = token.toByteArray();
-
-        TestCallback callback = new TestCallback();
-
-        // Request the install.
-        assertEquals(RulesManager.SUCCESS,
-                mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
-
-        // Simulate the async execution.
-        mFakeExecutor.simulateAsyncExecutionOfLastCommand();
-
-        // Verify nothing else happened.
-        verifyNoInstallerCallsMade();
-
-        // A failure to read the ParcelFileDescriptor is treated as a failure. It might be the
-        // result of a file system error. This is a fairly arbitrary choice.
-        verifyPackageTrackerCalled(token, false /* success */);
-
-        verifyNoPackageTrackerCallsMade();
-
-        // Check the callback was received.
-        callback.assertResultReceived(Callback.ERROR_UNKNOWN_FAILURE);
-    }
-
-    @Test
     public void requestUninstall_operationInProgress() throws Exception {
         configureCallerHasPermission();
 
@@ -753,6 +729,97 @@
         verifyPackageTrackerCalled(null /* token */, true /* success */);
     }
 
+    @Test
+    public void dump_noPermission() throws Exception {
+        when(mMockPermissionHelper.checkDumpPermission(any(String.class), any(PrintWriter.class)))
+                .thenReturn(false);
+
+        doDumpCallAndCapture(mRulesManagerService, null);
+        verifyZeroInteractions(mMockPackageTracker, mMockTimeZoneDistroInstaller);
+    }
+
+    @Test
+    public void dump_emptyArgs() throws Exception {
+        doSuccessfulDumpCall(mRulesManagerService, new String[0]);
+
+        // Verify the package tracker was consulted.
+        verify(mMockPackageTracker).dump(any(PrintWriter.class));
+    }
+
+    @Test
+    public void dump_nullArgs() throws Exception {
+        doSuccessfulDumpCall(mRulesManagerService, null);
+        // Verify the package tracker was consulted.
+        verify(mMockPackageTracker).dump(any(PrintWriter.class));
+    }
+
+    @Test
+    public void dump_unknownArgs() throws Exception {
+        String dumpedTextUnknownArgs = doSuccessfulDumpCall(
+                mRulesManagerService, new String[] { "foo", "bar"});
+
+        // Verify the package tracker was consulted.
+        verify(mMockPackageTracker).dump(any(PrintWriter.class));
+
+        String dumpedTextZeroArgs = doSuccessfulDumpCall(mRulesManagerService, null);
+        assertEquals(dumpedTextZeroArgs, dumpedTextUnknownArgs);
+    }
+
+    @Test
+    public void dump_formatState() throws Exception {
+        // Just expect these to not throw exceptions, not return nothing, and not interact with the
+        // package tracker.
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("p"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("s"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("c"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("i"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("o"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("t"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("a"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("z" /* Unknown */));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("piscotz"));
+
+        verifyZeroInteractions(mMockPackageTracker);
+    }
+
+    private static String[] dumpFormatArgs(String argsString) {
+        return new String[] { "-format_state", argsString};
+    }
+
+    private String doSuccessfulDumpCall(RulesManagerService rulesManagerService, String[] args)
+            throws Exception {
+        when(mMockPermissionHelper.checkDumpPermission(any(String.class), any(PrintWriter.class)))
+                .thenReturn(true);
+
+        // Set up the mocks to return (arbitrary) information about the current device state.
+        when(mMockTimeZoneDistroInstaller.getSystemRulesVersion()).thenReturn("2017a");
+        when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion()).thenReturn(
+                new DistroVersion(2, 3, "2017b", 4));
+        when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()).thenReturn(
+                StagedDistroOperation.install(new DistroVersion(5, 6, "2017c", 7)));
+
+        // Do the dump call.
+        String dumpedOutput = doDumpCallAndCapture(rulesManagerService, args);
+
+        assertFalse(dumpedOutput.isEmpty());
+
+        return dumpedOutput;
+    }
+
+    private static String doDumpCallAndCapture(
+            RulesManagerService rulesManagerService, String[] args) throws IOException {
+        File file = File.createTempFile("dump", null);
+        try {
+            try (FileOutputStream fos = new FileOutputStream(file)) {
+                FileDescriptor fd = fos.getFD();
+                rulesManagerService.dump(fd, args);
+            }
+            return IoUtils.readFileAsString(file.getAbsolutePath());
+        } finally {
+            file.delete();
+        }
+    }
+
     private void verifyNoPackageTrackerCallsMade() {
         verifyNoMoreInteractions(mMockPackageTracker);
         reset(mMockPackageTracker);
@@ -776,20 +843,9 @@
                 .enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
     }
 
-    private void configureParcelFileDescriptorReadSuccess(ParcelFileDescriptor parcelFileDescriptor,
-            byte[] content) throws Exception {
-        when(mMockFileDescriptorHelper.readFully(parcelFileDescriptor)).thenReturn(content);
-    }
-
-    private void configureParcelFileDescriptorReadFailure(ParcelFileDescriptor parcelFileDescriptor)
+    private void configureStageInstallExpectation(int resultCode)
             throws Exception {
-        when(mMockFileDescriptorHelper.readFully(parcelFileDescriptor))
-                .thenThrow(new IOException("Simulated failure"));
-    }
-
-    private void configureStageInstallExpectation(TimeZoneDistro expected, int resultCode)
-            throws Exception {
-        when(mMockTimeZoneDistroInstaller.stageInstallWithErrorCode(eq(expected)))
+        when(mMockTimeZoneDistroInstaller.stageInstallWithErrorCode(any(TimeZoneDistro.class)))
                 .thenReturn(resultCode);
     }
 
@@ -797,8 +853,8 @@
         doReturn(success).when(mMockTimeZoneDistroInstaller).stageUninstall();
     }
 
-    private void verifyStageInstallCalled(TimeZoneDistro expected) throws Exception {
-        verify(mMockTimeZoneDistroInstaller).stageInstallWithErrorCode(eq(expected));
+    private void verifyStageInstallCalled() throws Exception {
+        verify(mMockTimeZoneDistroInstaller).stageInstallWithErrorCode(any(TimeZoneDistro.class));
         verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
         reset(mMockTimeZoneDistroInstaller);
     }
@@ -830,10 +886,6 @@
         return new CheckToken(1, new PackageVersions(1, 1));
     }
 
-    private ParcelFileDescriptor createFakeParcelFileDescriptor() {
-        return new ParcelFileDescriptor((ParcelFileDescriptor) null);
-    }
-
     private void configureDeviceSystemRulesVersion(String systemRulesVersion) throws Exception {
         when(mMockTimeZoneDistroInstaller.getSystemRulesVersion()).thenReturn(systemRulesVersion);
     }
@@ -873,6 +925,10 @@
                 .thenThrow(new IOException("Simulated failure"));
     }
 
+    private static void assertClosed(ParcelFileDescriptor parcelFileDescriptor) {
+        assertFalse(parcelFileDescriptor.getFileDescriptor().valid());
+    }
+
     private static class FakeExecutor implements Executor {
 
         private Runnable mLastCommand;
@@ -929,4 +985,17 @@
             fail("Unexpected call");
         }
     }
+
+    private static ParcelFileDescriptor createParcelFileDescriptor(byte[] bytes)
+            throws IOException {
+        File file = File.createTempFile("pfd", null);
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(bytes);
+        }
+        ParcelFileDescriptor pfd =
+                ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+        // This should now be safe to delete. The ParcelFileDescriptor has an open fd.
+        file.delete();
+        return pfd;
+    }
 }
diff --git a/telecomm/java/android/telecom/Logging/EventManager.java b/telecomm/java/android/telecom/Logging/EventManager.java
index fddbfce..4fc3385 100644
--- a/telecomm/java/android/telecom/Logging/EventManager.java
+++ b/telecomm/java/android/telecom/Logging/EventManager.java
@@ -36,6 +36,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.TimeZone;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.stream.Collectors;
 
@@ -268,6 +269,7 @@
 
     public EventManager(@NonNull SessionManager.ISessionIdQueryHandler l) {
         mSessionIdHandler = l;
+        sDateFormat.setTimeZone(TimeZone.getDefault());
     }
 
     public void event(Loggable recordEntry, String event, Object data) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 17ad779..ecfdbaa 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1117,6 +1117,8 @@
     /** @hide */
     public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
     /** @hide */
+    public static final int IMSI_ENCRYPTION_DAYS_TIME_DISABLED = -1;
+    /** @hide */
     public static final int CDMA_ROAMING_MODE_ANY = 2;
     /**
      * Boolean indicating if support is provided for directly dialing FDN number from FDN list.
@@ -1371,6 +1373,23 @@
     public static final String KEY_ROAMING_OPERATOR_STRING_ARRAY =
             "roaming_operator_string_array";
 
+    /**
+     * URL from which the proto containing the public key of the Carrier used for
+     * IMSI encryption will be downloaded.
+     * @hide
+     */
+    public static final String IMSI_KEY_DOWNLOAD_URL_STRING = "imsi_key_download_url_string";
+
+    /**
+     * Time in days, after which the key will expire, and a new key will need to be downloaded.
+     * default value is {@link IMSI_ENCRYPTION_DAYS_TIME_DISABLED}, and indicates that IMSI
+     * encryption is not enabled by default for a carrier. Value of 0 indicates that the key
+     * does not expire.
+     * @hide
+     */
+    public static final String IMSI_KEY_EXPIRATION_DAYS_TIME_INT =
+            "imsi_key_expiration_days_time_int";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1609,6 +1628,8 @@
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
 	sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
+        sDefaults.putInt(IMSI_KEY_EXPIRATION_DAYS_TIME_INT, IMSI_ENCRYPTION_DAYS_TIME_DISABLED);
+        sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null);
     }
 
     /**
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index 79ee37a..4c3f7e7 100644
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
@@ -26,9 +27,9 @@
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.telephony.mbms.IDownloadCallback;
+import android.telephony.mbms.FileInfo;
 import android.telephony.mbms.DownloadRequest;
-import android.telephony.mbms.DownloadStatus;
+import android.telephony.mbms.IDownloadProgressListener;
 import android.telephony.mbms.IMbmsDownloadManagerCallback;
 import android.telephony.mbms.MbmsDownloadManagerCallback;
 import android.telephony.mbms.MbmsDownloadReceiver;
@@ -40,6 +41,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -93,19 +96,21 @@
     /**
      * Integer extra indicating the result code of the download. One of
      * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}.
+     * TODO: Not systemapi.
      */
     public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT";
 
     /**
      * Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result
      * is for. Must not be null.
-     * TODO: future systemapi (here and and all extras) except the two for the app intent
+     * TODO: Not systemapi.
      */
     public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO";
 
     /**
      * Extra containing the {@link DownloadRequest} for which the download result or file
      * descriptor request is for. Must not be null.
+     * TODO: future systemapi (here and and all extras) except the three for the app intent
      */
     public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
 
@@ -167,11 +172,20 @@
             "android.telephony.mbms.extra.TEMP_FILES_IN_USE";
 
     /**
+     * Extra containing an instance of {@link android.telephony.mbms.ServiceInfo}, used by
+     * file-descriptor requests and cleanup requests to specify which service they want to
+     * request temp files or clean up temp files for, respectively.
+     */
+    public static final String EXTRA_SERVICE_INFO =
+            "android.telephony.mbms.extra.SERVICE_INFO";
+
+    /**
      * Extra containing a single {@link Uri} indicating the location of the successfully
      * downloaded file. Set on the intent provided via
      * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}.
      * Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to
      * {@link #RESULT_SUCCESSFUL}.
+     * TODO: Not systemapi.
      */
     public static final String EXTRA_COMPLETED_FILE_URI =
             "android.telephony.mbms.extra.COMPLETED_FILE_URI";
@@ -181,32 +195,40 @@
     public static final int RESULT_EXPIRED    = 3;
     // TODO - more results!
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATUS_UNKNOWN, STATUS_ACTIVELY_DOWNLOADING, STATUS_PENDING_DOWNLOAD,
+            STATUS_PENDING_REPAIR, STATUS_PENDING_DOWNLOAD_WINDOW})
+    public @interface DownloadStatus {}
+
+    public static final int STATUS_UNKNOWN = 0;
+    public static final int STATUS_ACTIVELY_DOWNLOADING = 1;
+    public static final int STATUS_PENDING_DOWNLOAD = 2;
+    public static final int STATUS_PENDING_REPAIR = 3;
+    public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
+
     private final Context mContext;
     private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
 
     private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
-    private final IMbmsDownloadManagerCallback mCallback;
-    private final String mDownloadAppName;
+    private final MbmsDownloadManagerCallback mCallback;
 
-    private MbmsDownloadManager(Context context, IMbmsDownloadManagerCallback callback,
-            String downloadAppName, int subId) {
+    private MbmsDownloadManager(Context context, MbmsDownloadManagerCallback callback, int subId) {
         mContext = context;
         mCallback = callback;
-        mDownloadAppName = downloadAppName;
         mSubscriptionId = subId;
     }
 
     /**
      * Create a new MbmsDownloadManager using the system default data subscription ID.
-     * See {@link #create(Context, IMbmsDownloadManagerCallback, String, int)}
+     * See {@link #create(Context, MbmsDownloadManagerCallback, int)}
      *
      * @hide
      */
     public static MbmsDownloadManager create(Context context,
-            IMbmsDownloadManagerCallback listener, String downloadAppName)
+            MbmsDownloadManagerCallback listener)
             throws MbmsException {
-        return create(context, listener, downloadAppName,
-                SubscriptionManager.getDefaultSubscriptionId());
+        return create(context, listener, SubscriptionManager.getDefaultSubscriptionId());
     }
 
     /**
@@ -221,15 +243,13 @@
      *
      * @param context The instance of {@link Context} to use
      * @param listener A callback to get asynchronous error messages and file service updates.
-     * @param downloadAppName The app name, as negotiated with the eMBMS provider
      * @param subscriptionId The data subscription ID to use
      * @hide
      */
     public static MbmsDownloadManager create(Context context,
-            IMbmsDownloadManagerCallback listener, String downloadAppName, int subscriptionId)
+            MbmsDownloadManagerCallback listener, int subscriptionId)
             throws MbmsException {
-        MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
-                subscriptionId);
+        MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, subscriptionId);
         mdm.bindAndInitialize();
         return mdm;
     }
@@ -241,12 +261,22 @@
                     public void onServiceConnected(ComponentName name, IBinder service) {
                         IMbmsDownloadService downloadService =
                                 IMbmsDownloadService.Stub.asInterface(service);
+                        int result;
                         try {
-                            downloadService.initialize(
-                                    mDownloadAppName, mSubscriptionId, mCallback);
+                            result = downloadService.initialize(mSubscriptionId, mCallback);
                         } catch (RemoteException e) {
                             Log.e(LOG_TAG, "Service died before initialization");
                             return;
+                        } catch (RuntimeException e) {
+                            Log.e(LOG_TAG, "Runtime exception during initialization");
+                            mCallback.error(
+                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+                                    e.toString());
+                            return;
+                        }
+                        if (result != MbmsException.SUCCESS) {
+                            mCallback.error(result, "Error returned during initialization");
+                            return;
                         }
                         mService.set(downloadService);
                     }
@@ -267,17 +297,19 @@
      * The serviceClasses argument lets the app filter on types of programming and is opaque data
      * negotiated beforehand between the app and the carrier.
      *
-     * Multiple calls replace the list of serviceClasses of interest.
-     *
      * This may throw an {@link MbmsException} containing one of the following errors:
      * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
-     * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
-     * {@link MbmsException#ERROR_SERVICE_LOST}
+     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
      *
      * Asynchronous error codes via the {@link MbmsDownloadManagerCallback#error(int, String)}
      * callback can include any of the errors except:
-     * {@link MbmsException#ERROR_UNABLE_TO_START_SERVICE}
-     * {@link MbmsException#ERROR_END_OF_SESSION}
+     * {@link MbmsException.StreamingErrors#ERROR_UNABLE_TO_START_SERVICE}
+     *
+     * @param classList A list of service classes which the app wishes to receive
+     *                  {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)} callbacks
+     *                  about. Subsequent calls to this method will replace this list of service
+     *                  classes (i.e. the middleware will no longer send updates for services
+     *                  matching classes only in the old list).
      */
     public void getFileServices(List<String> classList) throws MbmsException {
         IMbmsDownloadService downloadService = mService.get();
@@ -285,15 +317,14 @@
             throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
         }
         try {
-            int returnCode = downloadService.getFileServices(
-                    mDownloadAppName, mSubscriptionId, classList);
+            int returnCode = downloadService.getFileServices(mSubscriptionId, classList);
             if (returnCode != MbmsException.SUCCESS) {
                 throw new MbmsException(returnCode);
             }
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
         }
     }
 
@@ -309,9 +340,10 @@
      * will default to a directory formed by the concatenation of the app's files directory and
      * {@link android.telephony.mbms.MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
      *
-     * This method may not be called while any download requests are still active. If this is
-     * the case, an {@link MbmsException} will be thrown with code
-     * {@link MbmsException#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
+     * Before calling this method, the app must cancel all of its pending
+     * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
+     * an {@link MbmsException} will be thrown with code
+     * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
      *
      * The {@link File} supplied as a root temp file directory must already exist. If not, an
      * {@link IllegalArgumentException} will be thrown.
@@ -337,14 +369,13 @@
         }
 
         try {
-            int result = downloadService.setTempFileRootDirectory(
-                    mDownloadAppName, mSubscriptionId, filePath);
+            int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
             if (result != MbmsException.SUCCESS) {
                 throw new MbmsException(result);
             }
         } catch (RemoteException e) {
             mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
         }
 
         SharedPreferences prefs = mContext.getSharedPreferences(
@@ -370,9 +401,10 @@
      * Asynchronous errors through the listener include any of the errors
      *
      * @param request The request that specifies what should be downloaded
-     * @param callback Optional callback that will provide progress updates if the app is running.
+     * @param progressListener Optional listener that will be provided progress updates
+     *                         if the app is running.
      */
-    public void download(DownloadRequest request, IDownloadCallback callback)
+    public void download(DownloadRequest request, IDownloadProgressListener progressListener)
             throws MbmsException {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
@@ -389,89 +421,140 @@
             setTempFileRootDirectory(tempRootDirectory);
         }
 
-        request.setAppName(mDownloadAppName);
-        // Check if the request is a multipart download. If so, validate that the destination is
-        // a directory that exists.
-        // TODO: figure out what qualifies a request as a multipart download request.
-        if (request.getSourceUri().getLastPathSegment() != null &&
-                request.getSourceUri().getLastPathSegment().contains("*")) {
-            File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
-            if (!toFile.isDirectory()) {
-                throw new IllegalArgumentException("Multipart download must specify valid " +
-                        "destination directory.");
-            }
-        }
-        // TODO: check to make sure destination is clear
-        // TODO: write download request token
+        checkValidDownloadDestination(request);
+        writeDownloadRequestToken(request);
         try {
-            downloadService.download(request, callback);
+            downloadService.download(request, progressListener);
         } catch (RemoteException e) {
             mService.set(null);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
         }
     }
 
     /**
-     * Returns a list DownloadRequests that originated from this application (UID).
-     *
-     * May throw a RemoteException.
-     *
-     * Asynchronous errors through the listener include any of the errors except
-     * <li>ERROR_UNABLED_TO_START_SERVICE</li>
-     * <li>ERROR_MSDC_INVALID_SERVICE_ID</li>
-     * <li>ERROR_MSDC_END_OF_SESSION</li>
+     * Returns a list of pending {@link DownloadRequest}s that originated from this application.
+     * A pending request is one that was issued via
+     * {@link #download(DownloadRequest, IDownloadCallback)} but not cancelled through
+     * {@link #cancelDownload(DownloadRequest)}.
+     * @return A list, possibly empty, of {@link DownloadRequest}s
      */
-    public List<DownloadRequest> listPendingDownloads() {
-        return null;
+    public @NonNull List<DownloadRequest> listPendingDownloads() throws MbmsException {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+        }
+
+        try {
+            return downloadService.listPendingDownloads(mSubscriptionId);
+        } catch (RemoteException e) {
+            mService.set(null);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+        }
     }
 
     /**
-     * Attempts to cancel the specified DownloadRequest.
+     * Attempts to cancel the specified {@link DownloadRequest}.
      *
-     * May throw a RemoteException.
+     * If the middleware is not aware of the specified download request, an MbmsException will be
+     * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
      *
-     * Synchronous responses may include
-     * <li>SUCCESS</li>
-     * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
-     * <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
+     * If this method returns without throwing an exception, you may assume that cancellation
+     * was successful.
+     * @param downloadRequest The download request that you wish to cancel.
      */
-    public int cancelDownload(DownloadRequest downloadRequest) {
-        return 0;
+    public void cancelDownload(DownloadRequest downloadRequest) throws MbmsException {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+        }
+
+        try {
+            int result = downloadService.cancelDownload(downloadRequest);
+            if (result != MbmsException.SUCCESS) {
+                throw new MbmsException(result);
+            }
+        } catch (RemoteException e) {
+            mService.set(null);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+        }
+        deleteDownloadRequestToken(downloadRequest);
     }
 
     /**
-     * Gets information about current and known upcoming downloads.
+     * Gets information about the status of a file pending download.
      *
-     * Current is a straightforward count of the files being downloaded "now"
-     * for some definition of now (may be racey).
-     * Future downloads include counts of files with pending repair operations, counts of
-     * files with future downloads and indication of scheduled download times with unknown
-     * file details.
+     * If the middleware has not yet been properly initialized or if it has no records of the
+     * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
+     * {@link #STATUS_UNKNOWN} will be returned.
      *
-     * May throw an IllegalArgumentException or RemoteException.
-     *
-     * If the DownloadRequest is unknown the results will be null.
+     * @param downloadRequest The download request to query.
+     * @param fileInfo The particular file within the request to get information on.
+     * @return The status of the download.
      */
-    public DownloadStatus getDownloadStatus(DownloadRequest downloadRequest) {
-        return null;
+    @DownloadStatus
+    public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo)
+            throws MbmsException {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+        }
+
+        try {
+            return downloadService.getDownloadStatus(downloadRequest, fileInfo);
+        } catch (RemoteException e) {
+            mService.set(null);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+        }
     }
 
     /**
-     * Resets middleware knowledge regarding this download request.
+     * Resets the middleware's knowledge of previously-downloaded files in this download request.
      *
-     * This state consists of knowledge of what files have already been downloaded.
-     * Normally the middleware won't download files who's hash matches previously downloaded
-     * content, even if that content has since been deleted.  If this function is called
-     * repeated content will be downloaded again when available.  This does not interrupt
-     * in-progress downloads.
+     * Normally, the middleware keeps track of the hashes of downloaded files and won't re-download
+     * files whose server-reported hash matches one of the already-downloaded files. This means
+     * that if the file is accidentally deleted by the user or by the app, the middleware will
+     * not try to download it again.
+     * This method will reset the middleware's cache of hashes for the provided
+     * {@link DownloadRequest}, so that previously downloaded content will be downloaded again
+     * when available.
+     * This will not interrupt in-progress downloads.
      *
-     * May throw an IllegalArgumentException or RemoteException.
+     * If the middleware is not aware of the specified download request, an MbmsException will be
+     * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
      *
-     * <li>SUCCESS</li>
-     * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
-     * <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
+     * May throw a {@link MbmsException} with error code
+     * @param downloadRequest The request to re-download files for.
      */
-    public int resetDownloadKnowledge(DownloadRequest downloadRequest) {
-        return 0;
+    public void resetDownloadKnowledge(DownloadRequest downloadRequest) throws MbmsException {
+        IMbmsDownloadService downloadService = mService.get();
+        if (downloadService == null) {
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+        }
+
+        try {
+            int result = downloadService.resetDownloadKnowledge(downloadRequest);
+            if (result != MbmsException.SUCCESS) {
+                throw new MbmsException(result);
+            }
+        } catch (RemoteException e) {
+            mService.set(null);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+        }
+    }
+
+    public void dispose() {
+        try {
+            IMbmsDownloadService downloadService = mService.get();
+            if (downloadService == null) {
+                Log.i(LOG_TAG, "Service already dead");
+                return;
+            }
+            downloadService.dispose(mSubscriptionId);
+            mService.set(null);
+        } catch (RemoteException e) {
+            // Ignore
+            Log.i(LOG_TAG, "Remote exception while disposing of service");
+        }
     }
 
     /**
@@ -504,18 +587,68 @@
         return null;
     }
 
-    public void dispose() {
+    private void writeDownloadRequestToken(DownloadRequest request) {
+        File token = getDownloadRequestTokenPath(request);
+        if (!token.getParentFile().exists()) {
+            token.getParentFile().mkdirs();
+        }
+        if (token.exists()) {
+            Log.w(LOG_TAG, "Download token " + token.getName() + " already exists");
+            return;
+        }
         try {
-            IMbmsDownloadService downloadService = mService.get();
-            if (downloadService == null) {
-                Log.i(LOG_TAG, "Service already dead");
-                return;
+            if (!token.createNewFile()) {
+                throw new RuntimeException("Failed to create download token for request "
+                        + request);
             }
-            downloadService.dispose(mDownloadAppName, mSubscriptionId);
-            mService.set(null);
-        } catch (RemoteException e) {
-            // Ignore
-            Log.i(LOG_TAG, "Remote exception while disposing of service");
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to create download token for request " + request
+                    + " due to IOException " + e);
+        }
+    }
+
+    private void deleteDownloadRequestToken(DownloadRequest request) {
+        File token = getDownloadRequestTokenPath(request);
+        if (!token.isFile()) {
+            Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token);
+            return;
+        }
+        if (!token.delete()) {
+            Log.w(LOG_TAG, "Couldn't delete download token at " + token);
+        }
+    }
+
+    private File getDownloadRequestTokenPath(DownloadRequest request) {
+        File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
+                request.getFileServiceId());
+        String downloadTokenFileName = request.getHash()
+                + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
+        return new File(tempFileLocation, downloadTokenFileName);
+    }
+
+    /**
+     * Verifies the following:
+     * If a request is multi-part,
+     *     1. Destination Uri must exist and be a directory
+     *     2. Directory specified must contain no files.
+     * Otherwise
+     *     1. The file specified by the destination Uri must not exist.
+     */
+    private void checkValidDownloadDestination(DownloadRequest request) {
+        File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
+        if (request.isMultipartDownload()) {
+            if (!toFile.isDirectory()) {
+                throw new IllegalArgumentException("Multipart download must specify valid " +
+                        "destination directory.");
+            }
+            if (toFile.listFiles().length > 0) {
+                throw new IllegalArgumentException("Destination directory must be clear of all " +
+                        "files.");
+            }
+        } else {
+            if (toFile.exists()) {
+                throw new IllegalArgumentException("Destination file must not exist.");
+            }
         }
     }
 }
diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java
index af7f333..d69562c 100644
--- a/telephony/java/android/telephony/MbmsStreamingManager.java
+++ b/telephony/java/android/telephony/MbmsStreamingManager.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
@@ -35,25 +37,33 @@
 
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-/** @hide */
+/**
+ * This class provides functionality for streaming media over MBMS.
+ */
 public class MbmsStreamingManager {
     private static final String LOG_TAG = "MbmsStreamingManager";
+
+    /**
+     * Service action which must be handled by the middleware implementing the MBMS streaming
+     * interface.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     public static final String MBMS_STREAMING_SERVICE_ACTION =
             "android.telephony.action.EmbmsStreaming";
 
     private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
     private MbmsStreamingManagerCallback mCallbackToApp;
-    private final String mAppName;
 
     private final Context mContext;
     private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
 
     /** @hide */
-    private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback listener,
-                    String streamingAppName, int subscriptionId) {
+    private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback,
+                    int subscriptionId) {
         mContext = context;
-        mAppName = streamingAppName;
-        mCallbackToApp = listener;
+        mCallbackToApp = callback;
         mSubscriptionId = subscriptionId;
     }
 
@@ -65,34 +75,33 @@
      * during the initialization or binding process.
      *
      * @param context The {@link Context} to use.
-     * @param listener A callback object on which you wish to receive results of asynchronous
+     * @param callback A callback object on which you wish to receive results of asynchronous
      *                 operations.
-     * @param streamingAppName The name of the streaming app, as specified by the carrier.
      * @param subscriptionId The subscription ID to use.
      */
     public static MbmsStreamingManager create(Context context,
-            MbmsStreamingManagerCallback listener, String streamingAppName, int subscriptionId)
+            MbmsStreamingManagerCallback callback, int subscriptionId)
             throws MbmsException {
-        MbmsStreamingManager manager = new MbmsStreamingManager(context, listener,
-                streamingAppName, subscriptionId);
+        MbmsStreamingManager manager = new MbmsStreamingManager(context, callback, subscriptionId);
         manager.bindAndInitialize();
         return manager;
     }
 
     /**
      * Create a new MbmsStreamingManager using the system default data subscription ID.
-     * See {@link #create(Context, MbmsStreamingManagerCallback, String, int)}.
+     * See {@link #create(Context, MbmsStreamingManagerCallback, int)}.
      */
     public static MbmsStreamingManager create(Context context,
-            MbmsStreamingManagerCallback listener, String streamingAppName)
+            MbmsStreamingManagerCallback callback)
             throws MbmsException {
-        return create(context, listener, streamingAppName,
-                SubscriptionManager.getDefaultSubscriptionId());
+        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId());
     }
 
     /**
      * Terminates this instance, ending calls to the registered listener.  Also terminates
      * any streaming services spawned from this instance.
+     *
+     * May throw an {@link IllegalStateException}
      */
     public void dispose() {
         IMbmsStreamingService streamingService = mService.get();
@@ -101,7 +110,7 @@
             return;
         }
         try {
-            streamingService.dispose(mAppName, mSubscriptionId);
+            streamingService.dispose(mSubscriptionId);
         } catch (RemoteException e) {
             // Ignore for now
         }
@@ -116,15 +125,15 @@
      *
      * Multiple calls replace the list of serviceClasses of interest.
      *
-     * This may throw an {@link MbmsException} containing one of the following errors:
-     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
-     * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
-     * {@link MbmsException#ERROR_SERVICE_LOST}
+     * 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}.
      *
-     * Asynchronous error codes via the {@link MbmsStreamingManagerCallback#error(int, String)}
-     * callback can include any of the errors except:
-     * {@link MbmsException#ERROR_UNABLE_TO_START_SERVICE}
-     * {@link MbmsException#ERROR_END_OF_SESSION}
+     * 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.
      */
     public void getStreamingServices(List<String> classList) throws MbmsException {
         IMbmsStreamingService streamingService = mService.get();
@@ -132,15 +141,14 @@
             throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
         }
         try {
-            int returnCode = streamingService.getStreamingServices(
-                    mAppName, mSubscriptionId, classList);
+            int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList);
             if (returnCode != MbmsException.SUCCESS) {
                 throw new MbmsException(returnCode);
             }
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
         }
     }
 
@@ -148,16 +156,24 @@
      * Starts streaming a requested service, reporting status to the indicated listener.
      * Returns an object used to control that stream. The stream may not be ready for consumption
      * immediately upon return from this method -- wait until the streaming state has been
-     * reported via {@link android.telephony.mbms.StreamingServiceCallback#streamStateUpdated(int)}
+     * reported via
+     * {@link android.telephony.mbms.StreamingServiceCallback#streamStateUpdated(int, int)}
      *
-     * May throw an {@link MbmsException} containing any of the following error codes:
-     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
-     * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
-     * {@link MbmsException#ERROR_SERVICE_LOST}
+     * 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}
      *
-     * Asynchronous errors through the listener include any of the errors
+     * Asynchronous errors through the listener include any of the errors in
+     * {@link android.telephony.mbms.MbmsException.GeneralErrors} or
+     * {@link android.telephony.mbms.MbmsException.StreamingErrors}.
+     *
+     * @param serviceInfo The information about the service to stream.
+     * @param listener A listener that'll be called when something about the stream changes.
+     * @return An instance of {@link StreamingService} through which the stream can be controlled.
      */
     public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
             StreamingServiceCallback listener) throws MbmsException {
@@ -168,18 +184,17 @@
 
         try {
             int returnCode = streamingService.startStreaming(
-                    mAppName, mSubscriptionId, serviceInfo.getServiceId(), listener);
+                    mSubscriptionId, serviceInfo.getServiceId(), listener);
             if (returnCode != MbmsException.SUCCESS) {
                 throw new MbmsException(returnCode);
             }
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService.set(null);
-            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
         }
 
-        return new StreamingService(
-                mAppName, mSubscriptionId, streamingService, serviceInfo, listener);
+        return new StreamingService(mSubscriptionId, streamingService, serviceInfo, listener);
     }
 
     private void bindAndInitialize() throws MbmsException {
@@ -189,13 +204,34 @@
                     public void onServiceConnected(ComponentName name, IBinder service) {
                         IMbmsStreamingService streamingService =
                                 IMbmsStreamingService.Stub.asInterface(service);
+                        int result;
                         try {
-                            streamingService.initialize(mCallbackToApp, mAppName, mSubscriptionId);
+                            result = streamingService.initialize(mCallbackToApp, mSubscriptionId);
                         } catch (RemoteException e) {
                             Log.e(LOG_TAG, "Service died before initialization");
                             return;
+                        } catch (RuntimeException e) {
+                            Log.e(LOG_TAG, "Runtime exception during initialization");
+                            try {
+                                mCallbackToApp.error(
+                                        MbmsException.InitializationErrors
+                                                .ERROR_UNABLE_TO_INITIALIZE,
+                                        e.toString());
+                            } catch (RemoteException e1) {
+                                // ignore
+                            }
+                            return;
                         }
-                        mService.set(null);
+                        if (result != MbmsException.SUCCESS) {
+                            try {
+                                mCallbackToApp.error(
+                                        result, "Error returned during initialization");
+                            } catch (RemoteException e) {
+                                // ignore
+                            }
+                            return;
+                        }
+                        mService.set(streamingService);
                     }
 
                     @Override
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index c905d3a..92a21b6 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -20,15 +20,18 @@
 
 import android.content.Context;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
 import android.util.SparseArray;
+import java.util.Arrays;
 import java.util.List;
 
 import com.android.internal.telephony.ITelephony;
@@ -42,6 +45,9 @@
     private static final String TAG = "TelephonyScanManager";
 
     /** @hide */
+    public static final String SCAN_RESULT_KEY = "scanResult";
+
+    /** @hide */
     public static final int CALLBACK_SCAN_RESULTS = 1;
     /** @hide */
     public static final int CALLBACK_SCAN_ERROR = 2;
@@ -112,7 +118,13 @@
                 switch (message.what) {
                     case CALLBACK_SCAN_RESULTS:
                         try {
-                            callback.onResults((List<CellInfo>) message.obj);
+                            final Bundle b = message.getData();
+                            final Parcelable[] parcelables = b.getParcelableArray(SCAN_RESULT_KEY);
+                            CellInfo[] ci = new CellInfo[parcelables.length];
+                            for (int i = 0; i < parcelables.length; i++) {
+                                ci[i] = (CellInfo) parcelables[i];
+                            }
+                            callback.onResults((List<CellInfo>) Arrays.asList(ci));
                         } catch (Exception e) {
                             Rlog.e(TAG, "Exception in networkscan callback onResults", e);
                         }
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxy.java b/telephony/java/android/telephony/ims/ImsServiceProxy.java
index a75cd86..038e295 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxy.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxy.java
@@ -90,11 +90,11 @@
                         " status: " + status);
                 if (mSlotId == slotId && feature == mSupportedFeature) {
                     mFeatureStatusCached = status;
+                    if (mStatusCallback != null) {
+                        mStatusCallback.notifyStatusChanged();
+                    }
                 }
             }
-            if (mStatusCallback != null) {
-                mStatusCallback.notifyStatusChanged();
-            }
         }
     };
 
@@ -129,7 +129,9 @@
     @Override
     public void endSession(int sessionId) throws RemoteException {
         synchronized (mLock) {
-            checkServiceIsReady();
+            // Only check to make sure the binder connection still exists. This method should
+            // still be able to be called when the state is STATE_NOT_AVAILABLE.
+            checkBinderConnection();
             getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
         }
     }
diff --git a/telephony/java/android/telephony/mbms/DownloadCallback.java b/telephony/java/android/telephony/mbms/DownloadCallback.java
deleted file mode 100644
index 0c6fec4..0000000
--- a/telephony/java/android/telephony/mbms/DownloadCallback.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2016 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.telephony.mbms;
-
-/**
- * A optional listener class used by download clients to track progress.
- * @hide
- */
-public class DownloadCallback extends IDownloadCallback.Stub {
-    /**
-     * Gives process callbacks for a given DownloadRequest.
-     * request indicates which download is being referenced.
-     * fileInfo gives information about the file being downloaded.  Note that
-     *   the request may result in many files being downloaded and the client
-     *   may not have been able to get a list of them in advance.
-     * downloadSize is the final amount to be downloaded.  This may be different
-     *   from the decoded final size, but is useful in gauging download progress.
-     * currentSize is the amount currently downloaded.
-     * decodedPercent is the percent from 0 to 100 of the file decoded.  After the
-     *   download completes the contents needs to be processed.  It is perhaps
-     *   uncompressed, transcoded and/or decrypted.  Generally the download completes
-     *   before the decode is started, but that's not required.
-     */
-    public void progress(DownloadRequest request, FileInfo fileInfo,
-            int downloadSize, int currentSize, int decodedPercent) {
-    }
-}
diff --git a/telephony/java/android/telephony/mbms/DownloadProgressListener.java b/telephony/java/android/telephony/mbms/DownloadProgressListener.java
new file mode 100644
index 0000000..d6bd5dc
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadProgressListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.telephony.mbms;
+
+/**
+ * A optional listener class used by download clients to track progress.
+ * @hide
+ */
+public class DownloadProgressListener extends IDownloadProgressListener.Stub {
+    /**
+     * Gives process callbacks for a given DownloadRequest.
+     * This is optionally specified when requesting a download and
+     * only lives while the app is running - it's unlikely to be useful for
+     * downloads far in the future.
+     *
+     * @param request a {@link DownloadRequest}, indicating which download is being referenced.
+     * @param fileInfo a {@link FileInfo} specifying the file to report progress on.  Note that
+     *   the request may result in many files being downloaded and the client
+     *   may not have been able to get a list of them in advance.
+     * @param currentDownloadSize is the current amount downloaded.
+     * @param fullDownloadSize is the total number of bytes that make up the downloaded content.
+     *   This may be different from the decoded final size, but is useful in gauging download
+     *   progress.
+     * @param currentDecodedSize is the number of bytes that have been decoded.
+     * @param fullDecodedSize is the total number of bytes that make up the final decoded content.
+     */
+    public void progress(DownloadRequest request, FileInfo fileInfo,
+            int currentDownloadSize, int fullDownloadSize,
+            int currentDecodedSize, int fullDecodedSize) {
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index c561741..01e0bbd 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -20,31 +20,74 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Base64;
+import android.util.Log;
 
-import java.lang.IllegalStateException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
 import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Objects;
 
 /**
  * A Parcelable class describing a pending Cell-Broadcast download request
  * @hide
  */
 public class DownloadRequest implements Parcelable {
+    // Version code used to keep token calculation consistent.
+    private static final int CURRENT_VERSION = 1;
+    private static final String LOG_TAG = "MbmsDownloadRequest";
+
+    /**
+     * Maximum permissible length for the app's download-completion intent, when serialized via
+     * {@link Intent#toUri(int)}.
+     */
+    public static final int MAX_APP_INTENT_SIZE = 50000;
+
+    /**
+     * Maximum permissible length for the app's destination path, when serialized via
+     * {@link Uri#toString()}.
+     */
+    public static final int MAX_DESTINATION_URI_SIZE = 50000;
+
     /** @hide */
+    private static class OpaqueDataContainer implements Serializable {
+        private final String destinationUri;
+        private final String appIntent;
+        private final int version;
+
+        public OpaqueDataContainer(String destinationUri, String appIntent, int version) {
+            this.destinationUri = destinationUri;
+            this.appIntent = appIntent;
+            this.version = version;
+        }
+    }
+
     public static class Builder {
-        private int id;
-        private FileServiceInfo serviceInfo;
+        private String fileServiceId;
         private Uri source;
         private Uri dest;
         private int subscriptionId;
         private String appIntent;
+        private int version = CURRENT_VERSION;
 
-        public Builder setId(int id) {
-            this.id = id;
+        public Builder setServiceInfo(FileServiceInfo serviceInfo) {
+            fileServiceId = serviceInfo.getServiceId();
             return this;
         }
 
-        public Builder setServiceInfo(FileServiceInfo serviceInfo) {
-            this.serviceInfo = serviceInfo;
+        /**
+         * @hide
+         * TODO: systemapi
+         */
+        public Builder setServiceId(String serviceId) {
+            fileServiceId = serviceId;
             return this;
         }
 
@@ -54,6 +97,10 @@
         }
 
         public Builder setDest(Uri dest) {
+            if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) {
+                throw new IllegalArgumentException("Destination uri must not exceed length " +
+                        MAX_DESTINATION_URI_SIZE);
+            }
             this.dest = dest;
             return this;
         }
@@ -65,33 +112,58 @@
 
         public Builder setAppIntent(Intent intent) {
             this.appIntent = intent.toUri(0);
+            if (this.appIntent.length() > MAX_APP_INTENT_SIZE) {
+                throw new IllegalArgumentException("App intent must not exceed length " +
+                        MAX_APP_INTENT_SIZE);
+            }
+            return this;
+        }
+
+        /**
+         * For use by middleware only
+         * TODO: systemapi
+         * @hide
+         */
+        public Builder setOpaqueData(byte[] data) {
+            try {
+                ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data));
+                OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();
+                version = dataContainer.version;
+                appIntent = dataContainer.appIntent;
+                dest = Uri.parse(dataContainer.destinationUri);
+            } catch (IOException e) {
+                // Really should never happen
+                Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
+                throw new IllegalArgumentException(e);
+            } catch (ClassNotFoundException e) {
+                Log.e(LOG_TAG, "Got ClassNotFoundException trying to parse opaque data");
+                throw new IllegalArgumentException(e);
+            }
             return this;
         }
 
         public DownloadRequest build() {
-            return new DownloadRequest(id, serviceInfo, source, dest,
-                    subscriptionId, appIntent, null);
+            return new DownloadRequest(fileServiceId, source, dest,
+                    subscriptionId, appIntent, version);
         }
     }
 
-    private final int downloadId;
-    private final FileServiceInfo fileServiceInfo;
+    private final String fileServiceId;
     private final Uri sourceUri;
     private final Uri destinationUri;
     private final int subscriptionId;
     private final String serializedResultIntentForApp;
-    private String appName; // not the Android app Name, the embms app name
+    private final int version;
 
-    private DownloadRequest(int id, FileServiceInfo serviceInfo,
+    private DownloadRequest(String fileServiceId,
             Uri source, Uri dest,
-            int sub, String appIntent, String name) {
-        downloadId = id;
-        fileServiceInfo = serviceInfo;
+            int sub, String appIntent, int version) {
+        this.fileServiceId = fileServiceId;
         sourceUri = source;
         destinationUri = dest;
         subscriptionId = sub;
         serializedResultIntentForApp = appIntent;
-        appName = name;
+        this.version = version;
     }
 
     public static DownloadRequest copy(DownloadRequest other) {
@@ -99,23 +171,21 @@
     }
 
     private DownloadRequest(DownloadRequest dr) {
-        downloadId = dr.downloadId;
-        fileServiceInfo = dr.fileServiceInfo;
+        fileServiceId = dr.fileServiceId;
         sourceUri = dr.sourceUri;
         destinationUri = dr.destinationUri;
         subscriptionId = dr.subscriptionId;
         serializedResultIntentForApp = dr.serializedResultIntentForApp;
-        appName = dr.appName;
+        version = dr.version;
     }
 
     private DownloadRequest(Parcel in) {
-        downloadId = in.readInt();
-        fileServiceInfo = in.readParcelable(getClass().getClassLoader());
+        fileServiceId = in.readString();
         sourceUri = in.readParcelable(getClass().getClassLoader());
         destinationUri = in.readParcelable(getClass().getClassLoader());
         subscriptionId = in.readInt();
         serializedResultIntentForApp = in.readString();
-        appName = in.readString();
+        version = in.readInt();
     }
 
     public int describeContents() {
@@ -123,21 +193,16 @@
     }
 
     public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(downloadId);
-        out.writeParcelable(fileServiceInfo, flags);
+        out.writeString(fileServiceId);
         out.writeParcelable(sourceUri, flags);
         out.writeParcelable(destinationUri, flags);
         out.writeInt(subscriptionId);
         out.writeString(serializedResultIntentForApp);
-        out.writeString(appName);
+        out.writeInt(version);
     }
 
-    public int getDownloadId() {
-        return downloadId;
-    }
-
-    public FileServiceInfo getFileServiceInfo() {
-        return fileServiceInfo;
+    public String getFileServiceId() {
+        return fileServiceId;
     }
 
     public Uri getSourceUri() {
@@ -160,16 +225,29 @@
         }
     }
 
-    /** @hide */
-    public synchronized void setAppName(String newAppName) {
-        if (appName != null) {
-            throw new IllegalStateException("Attempting to reset appName");
+    /**
+     * @hide
+     * TODO: systemapi
+     */
+    public byte[] getOpaqueData() {
+        try {
+            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+            ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
+            OpaqueDataContainer container = new OpaqueDataContainer(
+                    destinationUri.toString(), serializedResultIntentForApp, version);
+            stream.writeObject(container);
+            stream.flush();
+            return byteArrayOutputStream.toByteArray();
+        } catch (IOException e) {
+            // Really should never happen
+            Log.e(LOG_TAG, "Got IOException trying to serialize opaque data");
+            return null;
         }
-        appName = newAppName;
     }
 
-    public String getAppName() {
-        return appName;
+    /** @hide */
+    public int getVersion() {
+        return version;
     }
 
     public static final Parcelable.Creator<DownloadRequest> CREATOR =
@@ -181,4 +259,59 @@
             return new DownloadRequest[size];
         }
     };
+
+    /**
+     * @hide
+     */
+    public boolean isMultipartDownload() {
+        // TODO: figure out what qualifies a request as a multipart download request.
+        return getSourceUri().getLastPathSegment() != null &&
+                getSourceUri().getLastPathSegment().contains("*");
+    }
+
+    /**
+     * Retrieves the hash string that should be used as the filename when storing a token for
+     * this DownloadRequest.
+     * @hide
+     */
+    public String getHash() {
+        MessageDigest digest;
+        try {
+            digest = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Could not get sha256 hash object");
+        }
+        if (version >= 1) {
+            // Hash the source URI, destination URI, and the app intent
+            digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
+            digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
+            digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+        }
+        // Add updates for future versions here
+        return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) {
+            return false;
+        }
+        if (!(o instanceof DownloadRequest)) {
+            return false;
+        }
+        DownloadRequest request = (DownloadRequest) o;
+        return subscriptionId == request.subscriptionId &&
+                version == request.version &&
+                Objects.equals(fileServiceId, request.fileServiceId) &&
+                Objects.equals(sourceUri, request.sourceUri) &&
+                Objects.equals(destinationUri, request.destinationUri) &&
+                Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(fileServiceId, sourceUri, destinationUri,
+                subscriptionId, serializedResultIntentForApp, version);
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/DownloadStatus.aidl b/telephony/java/android/telephony/mbms/DownloadStatus.aidl
deleted file mode 100755
index e7cfd39..0000000
--- a/telephony/java/android/telephony/mbms/DownloadStatus.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
-** Copyright 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.telephony.mbms;
-
-parcelable DownloadStatus;
diff --git a/telephony/java/android/telephony/mbms/DownloadStatus.java b/telephony/java/android/telephony/mbms/DownloadStatus.java
deleted file mode 100644
index 90eb53f..0000000
--- a/telephony/java/android/telephony/mbms/DownloadStatus.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.telephony.mbms;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A Parcelable class describing the status of a Cell-Broadcast download request
- * @hide
- */
-public class DownloadStatus implements Parcelable {
-    // includes downloads and active repair work
-    public final int activelyDownloading;
-
-    // files scheduled for future broadcast
-    public final int pendingDownloads;
-
-    // files scheduled for future repairs
-    public final int pendingRepairs;
-
-    // is a future download window scheduled with unknown
-    // number of files
-    public final boolean windowPending;
-
-    public DownloadStatus(int downloading, int downloads, int repairs, boolean window) {
-        activelyDownloading = downloading;
-        pendingDownloads = downloads;
-        pendingRepairs = repairs;
-        windowPending = window;
-    }
-
-    public static final Parcelable.Creator<DownloadStatus> CREATOR =
-            new Parcelable.Creator<DownloadStatus>() {
-        @Override
-        public DownloadStatus createFromParcel(Parcel in) {
-            return new DownloadStatus(in);
-        }
-
-        @Override
-        public DownloadStatus[] newArray(int size) {
-            return new DownloadStatus[size];
-        }
-    };
-
-    DownloadStatus(Parcel in) {
-        activelyDownloading = in.readInt();
-        pendingDownloads = in.readInt();
-        pendingRepairs = in.readInt();
-        windowPending = (in.readInt() == 1);
-    }
-
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(activelyDownloading);
-        dest.writeInt(pendingDownloads);
-        dest.writeInt(pendingRepairs);
-        dest.writeInt((windowPending ? 1 : 0));
-    }
-
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java
index 1b87393..b8e1c49 100644
--- a/telephony/java/android/telephony/mbms/FileInfo.java
+++ b/telephony/java/android/telephony/mbms/FileInfo.java
@@ -61,6 +61,10 @@
         }
     };
 
+    /**
+     * @hide
+     * TODO: systemapi
+     */
     public FileInfo(Uri uri, String mimeType, long size, byte[] md5Hash) {
         this.uri = uri;
         this.mimeType = mimeType;
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index 6646dc8..8afe4d3 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -32,6 +32,7 @@
 public class FileServiceInfo extends ServiceInfo implements Parcelable {
     private final List<FileInfo> files;
 
+    /** @hide TODO: systemapi */
     public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
             List<Locale> newLocales, String newServiceId, Date start, Date end,
             List<FileInfo> newFiles) {
diff --git a/telephony/java/android/telephony/mbms/IDownloadCallback.aidl b/telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl
similarity index 87%
rename from telephony/java/android/telephony/mbms/IDownloadCallback.aidl
rename to telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl
index a6bd7e5..bb9dc6c 100755
--- a/telephony/java/android/telephony/mbms/IDownloadCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl
@@ -23,12 +23,12 @@
  * The optional interface used by download clients to track progress.
  * @hide
  */
-interface IDownloadCallback
+interface IDownloadProgressListener
 {
     /**
      * Gives progress callbacks for a given DownloadRequest.  Includes a FileInfo
      * as the list of files may not have been known at request-time.
      */
-    void progress(in DownloadRequest request, in FileInfo fileInfo, int downloadSize,
-            int currentSize, int decodedPercent);
+    void progress(in DownloadRequest request, in FileInfo fileInfo, int currentDownloadSize,
+            int fullDownloadSize, int currentDecodedSize, int fullDecodedSize);
 }
diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
index 8116a7f..007aee7 100755
--- a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
@@ -30,7 +30,5 @@
 
     void streamingServicesUpdated(in List<StreamingServiceInfo> services);
 
-    void activeStreamingServicesUpdated(in List<StreamingServiceInfo> services);
-
     void middlewareReady();
 }
diff --git a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
index 94c80f4..0952fbe 100755
--- a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
@@ -21,7 +21,7 @@
  */
 oneway interface IStreamingServiceCallback {
     void error(int errorCode, String message);
-    void streamStateUpdated(int state);
+    void streamStateUpdated(int state, int reason);
     void mediaDescriptionUpdated();
     void broadcastSignalStrengthUpdated(int signalStrength);
     void streamMethodUpdated(int methodType);
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
index 5b22199..ba25f66 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
@@ -55,7 +55,7 @@
      * Before this method is called, calling any method on an instance of
      * {@link android.telephony.MbmsDownloadManager} will result in an {@link MbmsException}
      * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
-     * or {@link MbmsException#ERROR_MIDDLEWARE_NOT_YET_READY}
+     * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
      */
     @Override
     public void middlewareReady() {
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index b51c367..339ff39 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -28,7 +28,12 @@
 import android.util.Log;
 
 import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -39,24 +44,69 @@
  * @hide
  */
 public class MbmsDownloadReceiver extends BroadcastReceiver {
+    public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token";
+    public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
+
+    /**
+     * TODO: @SystemApi all these result codes
+     * Indicates that the requested operation completed without error.
+     */
+    public static final int RESULT_OK = 0;
+
+    /**
+     * Indicates that the intent sent had an invalid action. This will be the result if
+     * {@link Intent#getAction()} returns anything other than
+     * {@link MbmsDownloadManager#ACTION_DOWNLOAD_RESULT_INTERNAL},
+     * {@link MbmsDownloadManager#ACTION_FILE_DESCRIPTOR_REQUEST}, or
+     * {@link MbmsDownloadManager#ACTION_CLEANUP}.
+     * This is a fatal result code and no result extras should be expected.
+     */
+    public static final int RESULT_INVALID_ACTION = 1;
+
+    /**
+     * Indicates that the intent was missing some required extras.
+     * This is a fatal result code and no result extras should be expected.
+     */
+    public static final int RESULT_MALFORMED_INTENT = 2;
+
+    /**
+     * Indicates that the supplied value for {@link MbmsDownloadManager#EXTRA_TEMP_FILE_ROOT}
+     * does not match what the app has stored.
+     * This is a fatal result code and no result extras should be expected.
+     */
+    public static final int RESULT_BAD_TEMP_FILE_ROOT = 3;
+
+    /**
+     * Indicates that the manager was unable to move the completed download to its final location.
+     * This is a fatal result code and no result extras should be expected.
+     */
+    public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4;
+
+    /**
+     * Indicates that the manager was unable to generate one or more of the requested file
+     * descriptors.
+     * This is a non-fatal result code -- some file descriptors may still be generated, but there
+     * is no guarantee that they will be the same number as requested.
+     */
+    public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5;
+
     private static final String LOG_TAG = "MbmsDownloadReceiver";
     private static final String TEMP_FILE_SUFFIX = ".embms.temp";
     private static final int MAX_TEMP_FILE_RETRIES = 5;
 
-    public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
 
     private String mFileProviderAuthorityCache = null;
     private String mMiddlewarePackageNameCache = null;
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (!verifyIntentContents(intent)) {
-            setResultCode(1 /* TODO: define error constants */);
+        if (!verifyIntentContents(context, intent)) {
+            setResultCode(RESULT_MALFORMED_INTENT);
             return;
         }
         if (!Objects.equals(intent.getStringExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT),
                 MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) {
-            setResultCode(1 /* TODO: define error constants */);
+            setResultCode(RESULT_BAD_TEMP_FILE_ROOT);
             return;
         }
 
@@ -65,11 +115,14 @@
             cleanupPostMove(context, intent);
         } else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
             generateTempFiles(context, intent);
+        } else if (MbmsDownloadManager.ACTION_CLEANUP.equals(intent.getAction())) {
+            cleanupTempFiles(context, intent);
+        } else {
+            setResultCode(RESULT_INVALID_ACTION);
         }
-        // TODO: Add handling for ACTION_CLEANUP
     }
 
-    private boolean verifyIntentContents(Intent intent) {
+    private boolean verifyIntentContents(Context context, Intent intent) {
         if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
             if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) {
                 Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
@@ -93,26 +146,47 @@
                         "temp file. Ignoring.");
                 return false;
             }
-            return true;
+            DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+            String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
+            File expectedTokenFile = new File(
+                    MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()),
+                    expectedTokenFileName);
+            if (!expectedTokenFile.exists()) {
+                Log.w(LOG_TAG, "Supplied download request does not match a token that we have. " +
+                        "Expected " + expectedTokenFile);
+                return false;
+            }
         } else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_REQUEST)) {
-                Log.w(LOG_TAG, "Temp file request not include the associated request. Ignoring.");
+            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO)) {
+                Log.w(LOG_TAG, "Temp file request did not include the associated service info." +
+                        " Ignoring.");
                 return false;
             }
             if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) {
                 Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
                 return false;
             }
-            return true;
+        } else if (MbmsDownloadManager.ACTION_CLEANUP.equals(intent.getAction())) {
+            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO)) {
+                Log.w(LOG_TAG, "Cleanup request did not include the associated service info." +
+                        " Ignoring.");
+                return false;
+            }
+            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) {
+                Log.w(LOG_TAG, "Cleanup request did not include the temp file root. Ignoring.");
+                return false;
+            }
+            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILES_IN_USE)) {
+                Log.w(LOG_TAG, "Cleanup request did not include the list of temp files in use. " +
+                        "Ignoring.");
+                return false;
+            }
         }
-
-        Log.w(LOG_TAG, "Received intent with unknown action: " + intent.getAction());
-        return false;
+        return true;
     }
 
     private void moveDownloadedFile(Context context, Intent intent) {
         DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
-        // TODO: check request against token
         Intent intentForApp = request.getIntentForApp();
 
         int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
@@ -127,29 +201,30 @@
 
         Uri destinationUri = request.getDestinationUri();
         Uri finalTempFile = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FINAL_URI);
-        if (!verifyTempFilePath(context, request, finalTempFile)) {
+        if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {
             Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
-            setResultCode(1);
+            setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
             return;
         }
 
-        String relativePath = calculateDestinationFileRelativePath(request,
-                (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO));
+        FileInfo completedFileInfo =
+                (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO);
+        String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo);
 
         Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath);
         if (finalFileLocation == null) {
             Log.w(LOG_TAG, "Failed to move temp file to final destination");
-            // TODO: how do we notify the app of this?
-            setResultCode(1);
+            setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
+            return;
         }
         intentForApp.putExtra(MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI, finalFileLocation);
+        intentForApp.putExtra(MbmsDownloadManager.EXTRA_FILE_INFO, completedFileInfo);
 
         context.sendBroadcast(intentForApp);
-        setResultCode(0);
+        setResultCode(RESULT_OK);
     }
 
     private void cleanupPostMove(Context context, Intent intent) {
-        // TODO: account for in-use temp files
         DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
         if (request == null) {
             Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
@@ -162,7 +237,7 @@
         }
 
         for (Uri tempFileUri : tempFiles) {
-            if (verifyTempFilePath(context, request, tempFileUri)) {
+            if (verifyTempFilePath(context, request.getFileServiceId(), tempFileUri)) {
                 File tempFile = new File(tempFileUri.getSchemeSpecificPart());
                 tempFile.delete();
             }
@@ -170,11 +245,12 @@
     }
 
     private void generateTempFiles(Context context, Intent intent) {
-        // TODO: update pursuant to final decision on temp file locations
-        DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
-        if (request == null) {
-            Log.w(LOG_TAG, "Temp file request did not include the associated request. Ignoring.");
-            setResultCode(1 /* TODO: define error constants */);
+        FileServiceInfo serviceInfo =
+                intent.getParcelableExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO);
+        if (serviceInfo == null) {
+            Log.w(LOG_TAG, "Temp file request did not include the associated service info. " +
+                    "Ignoring.");
+            setResultCode(RESULT_MALFORMED_INTENT);
             return;
         }
         int fdCount = intent.getIntExtra(MbmsDownloadManager.EXTRA_FD_COUNT, 0);
@@ -182,24 +258,28 @@
 
         if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
             Log.i(LOG_TAG, "No temp files actually requested. Ending.");
-            setResultCode(0);
+            setResultCode(RESULT_OK);
             setResultExtras(Bundle.EMPTY);
             return;
         }
 
-        ArrayList<UriPathPair> freshTempFiles = generateFreshTempFiles(context, request, fdCount);
+        ArrayList<UriPathPair> freshTempFiles =
+                generateFreshTempFiles(context, serviceInfo, fdCount);
         ArrayList<UriPathPair> pausedFiles =
-                generateUrisForPausedFiles(context, request, pausedList);
+                generateUrisForPausedFiles(context, serviceInfo, pausedList);
 
         Bundle result = new Bundle();
         result.putParcelableArrayList(MbmsDownloadManager.EXTRA_FREE_URI_LIST, freshTempFiles);
         result.putParcelableArrayList(MbmsDownloadManager.EXTRA_PAUSED_URI_LIST, pausedFiles);
+        setResultCode(RESULT_OK);
         setResultExtras(result);
     }
 
-    private ArrayList<UriPathPair> generateFreshTempFiles(Context context, DownloadRequest request,
+    private ArrayList<UriPathPair> generateFreshTempFiles(Context context,
+            FileServiceInfo serviceInfo,
             int freshFdCount) {
-        File tempFileDir = getEmbmsTempFileDirForRequest(context, request);
+        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
+                serviceInfo.getServiceId());
         if (!tempFileDir.exists()) {
             tempFileDir.mkdirs();
         }
@@ -210,11 +290,11 @@
         for (int i = 0; i < freshFdCount; i++) {
             File tempFile = generateSingleTempFile(tempFileDir);
             if (tempFile == null) {
-                setResultCode(2 /* TODO: define error constants */);
+                setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
                 Log.w(LOG_TAG, "Failed to generate a temp file. Moving on.");
                 continue;
             }
-            Uri fileUri = Uri.fromParts(ContentResolver.SCHEME_FILE, tempFile.getPath(), null);
+            Uri fileUri = Uri.fromFile(tempFile);
             Uri contentUri = MbmsTempFileProvider.getUriForFile(
                     context, getFileProviderAuthorityCached(context), tempFile);
             context.grantUriPermission(getMiddlewarePackageCached(context), contentUri,
@@ -243,22 +323,22 @@
     }
 
     private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context,
-            DownloadRequest request, List<Uri> pausedFiles) {
+            FileServiceInfo serviceInfo, List<Uri> pausedFiles) {
         if (pausedFiles == null) {
             return new ArrayList<>(0);
         }
         ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size());
 
         for (Uri fileUri : pausedFiles) {
-            if (!verifyTempFilePath(context, request, fileUri)) {
+            if (!verifyTempFilePath(context, serviceInfo.getServiceId(), fileUri)) {
                 Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume");
-                setResultCode(2 /* TODO: define error codes */);
+                setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
                 continue;
             }
             File tempFile = new File(fileUri.getSchemeSpecificPart());
             if (!tempFile.exists()) {
                 Log.w(LOG_TAG, "Supplied file " + fileUri + " does not exist.");
-                setResultCode(2 /* TODO: define error codes */);
+                setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
                 continue;
             }
             Uri contentUri = MbmsTempFileProvider.getUriForFile(
@@ -271,6 +351,39 @@
         return result;
     }
 
+    private void cleanupTempFiles(Context context, Intent intent) {
+        FileServiceInfo serviceInfo =
+                intent.getParcelableExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO);
+        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
+                serviceInfo.getServiceId());
+        final List<Uri> filesInUse =
+                intent.getParcelableArrayListExtra(MbmsDownloadManager.EXTRA_TEMP_FILES_IN_USE);
+        File[] filesToDelete = tempFileDir.listFiles(new FileFilter() {
+            @Override
+            public boolean accept(File file) {
+                File canonicalFile;
+                try {
+                    canonicalFile = file.getCanonicalFile();
+                } catch (IOException e) {
+                    Log.w(LOG_TAG, "Got IOException canonicalizing " + file + ", not deleting.");
+                    return false;
+                }
+                // Reject all files that don't match what we think a temp file should look like
+                // e.g. download tokens
+                if (!canonicalFile.getName().endsWith(TEMP_FILE_SUFFIX)) {
+                    return false;
+                }
+                // If any of the files in use match the uri, return false to reject it from the
+                // list to delete.
+                Uri fileInUseUri = Uri.fromFile(canonicalFile);
+                return !filesInUse.contains(fileInUseUri);
+            }
+        });
+        for (File fileToDelete : filesToDelete) {
+            fileToDelete.delete();
+        }
+    }
+
     private static String calculateDestinationFileRelativePath(DownloadRequest request,
             FileInfo info) {
         List<String> filePathComponents = info.getUri().getPathSegments();
@@ -322,17 +435,16 @@
         }
         toFile.getParentFile().mkdirs();
 
-        // TODO: This will not work if the two files are on different filesystems. Add manual
-        // copy later.
         if (fromFile.renameTo(toFile)) {
             return Uri.fromFile(toFile);
+        } else if (manualMove(fromFile, toFile)) {
+            return Uri.fromFile(toFile);
         }
         return null;
     }
 
-    private static boolean verifyTempFilePath(Context context, DownloadRequest request,
+    private static boolean verifyTempFilePath(Context context, String serviceId,
             Uri filePath) {
-        // TODO: modify pursuant to final decision on temp file path scheme
         if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) {
             Log.w(LOG_TAG, "Uri " + filePath + " does not have a file scheme");
             return false;
@@ -345,24 +457,14 @@
             return false;
         }
 
-        if (!MbmsUtils.isContainedIn(getEmbmsTempFileDirForRequest(context, request), tempFile)) {
+        if (!MbmsUtils.isContainedIn(
+                MbmsUtils.getEmbmsTempFileDirForService(context, serviceId), tempFile)) {
             return false;
         }
 
         return true;
     }
 
-    /**
-     * Returns a File linked to the directory used to store temp files for this request
-     */
-    private static File getEmbmsTempFileDirForRequest(Context context, DownloadRequest request) {
-        File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
-
-        // TODO: better naming scheme for temp file dirs
-        String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId());
-        return new File(embmsTempFileDir, tempFileDirName);
-    }
-
     private String getFileProviderAuthorityCached(Context context) {
         if (mFileProviderAuthorityCache != null) {
             return mFileProviderAuthorityCache;
@@ -394,4 +496,40 @@
         }
         return mMiddlewarePackageNameCache;
     }
+
+    private static boolean manualMove(File src, File dst) {
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            if (!dst.exists()) {
+                dst.createNewFile();
+            }
+            in = new FileInputStream(src);
+            out = new FileOutputStream(dst);
+            byte[] buffer = new byte[2048];
+            int len;
+            do {
+                len = in.read(buffer);
+                out.write(buffer, 0, len);
+            } while (len > 0);
+        } catch (IOException e) {
+            Log.w(LOG_TAG, "Manual file move failed due to exception "  + e);
+            if (dst.exists()) {
+                dst.delete();
+            }
+            return false;
+        } finally {
+            try {
+                if (in != null) {
+                    in.close();
+                }
+                if (out != null) {
+                    out.close();
+                }
+            } catch (IOException e) {
+                Log.w(LOG_TAG, "Error closing streams: " + e);
+            }
+        }
+        return true;
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java
index 8260b72..f51563a 100644
--- a/telephony/java/android/telephony/mbms/MbmsException.java
+++ b/telephony/java/android/telephony/mbms/MbmsException.java
@@ -16,33 +16,119 @@
 
 package android.telephony.mbms;
 
-/** @hide */
 public class MbmsException extends Exception {
+    /** Indicates that the operation was successful. */
     public static final int SUCCESS = 0;
-    public static final int ERROR_NO_SERVICE_INSTALLED = 1;
-    public static final int ERROR_MULTIPLE_SERVICES_INSTALLED = 2;
-    public static final int ERROR_BIND_TIMEOUT_OR_FAILURE = 3;
-    public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 4;
-    public static final int ERROR_ALREADY_INITIALIZED = 5;
-    public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 6;
-    public static final int ERROR_MIDDLEWARE_NOT_BOUND = 7;
-    public static final int ERROR_UNABLE_TO_START_SERVICE = 8;
-    public static final int ERROR_STREAM_ALREADY_STARTED = 9;
-    public static final int ERROR_END_OF_SESSION = 10;
-    public static final int ERROR_SERVICE_LOST = 11;
-    public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 12;
-    public static final int ERROR_IN_E911 = 13;
-    public static final int ERROR_OUT_OF_MEMORY = 14;
-    public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 15;
-    public static final int ERROR_UNABLE_TO_READ_SIM = 16;
-    public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 17;
-    public static final int ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT = 18;
+
+    // Following errors are generated in the manager and should not be returned from the
+    // middleware
+    /**
+     * Indicates that either no MBMS middleware app is installed on the device or multiple
+     * middleware apps are installed on the device.
+     */
+    public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1;
+
+    /**
+     * 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.
+     */
+    public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2;
+
+    /** Indicates that the middleware has died and the requested operation was not completed.*/
+    public static final int ERROR_MIDDLEWARE_LOST = 3;
+
+    /**
+     * Indicates errors that may be generated during initialization by the
+     * middleware. They are applicable to both streaming and file-download use-cases.
+     */
+    public static class InitializationErrors {
+        /**
+         * Indicates that the app tried to create more than one instance each of
+         * {@link android.telephony.MbmsStreamingManager} or
+         * TODO: link android.telephony.MbmsDownloadManager
+         */
+        public static final int ERROR_DUPLICATE_INITIALIZE = 101;
+        /** Indicates that the app is not authorized to access media via MBMS.*/
+        public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102;
+        /** Indicates that the middleware was unable to initialize for this app. */
+        public static final int ERROR_UNABLE_TO_INITIALIZE = 103;
+    }
+
+    /**
+     * Indicates the errors that may occur at any point and are applicable to both
+     * streaming and file-download.
+     */
+    public static class GeneralErrors {
+        /**
+         * Indicates that the app attempted to perform an operation before receiving notification
+         * that the middleware is ready via {@link MbmsStreamingManagerCallback#middlewareReady()}
+         * or TODO: link MbmsDownloadManagerCallback#middlewareReady
+         */
+        public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201;
+        /**
+         * Indicates that the middleware ran out of memory and was unable to complete the requested
+         * operation.
+         */
+        public static final int ERROR_OUT_OF_MEMORY = 202;
+        /**
+         * Indicates that the requested operation failed due to the middleware being unavailable due
+         * to a transient condition. The app may retry the operation at a later time.
+         */
+        public static final int ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE = 203;
+        /**
+         * Indicates that the requested operation was not performed due to being in emergency
+         * callback mode.
+         */
+        public static final int ERROR_IN_E911 = 204;
+        /** Indicates that MBMS is not available due to the device being in roaming. */
+        public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 205;
+        /** Indicates that MBMS is not available due to a SIM read error. */
+        public static final int ERROR_UNABLE_TO_READ_SIM = 206;
+        /**
+         * Indicates that MBMS is not available due to the inserted SIM being from an unsupported
+         * carrier.
+         */
+        public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207;
+    }
+
+    /**
+     * Indicates the errors that are applicable only to the streaming use-case
+     */
+    public static class StreamingErrors {
+        /** Indicates that the middleware cannot start a stream due to too many ongoing streams */
+        public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301;
+
+        /** Indicates that the middleware was unable to start the streaming service */
+        public static final int ERROR_UNABLE_TO_START_SERVICE = 302;
+
+        /**
+         * Indicates that the app called
+         * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo, StreamingServiceCallback)}
+         * more than once for the same {@link StreamingServiceInfo}.
+         */
+        public static final int ERROR_DUPLICATE_START_STREAM = 303;
+    }
+
+    /**
+     * Indicates the errors that are applicable only to the file-download use-case
+     * TODO: unhide
+     * @hide
+     */
+    public static class DownloadErrors {
+        /**
+         * Indicates that the app is not allowed to change the temp file root at this time due to
+         * outstanding download requests.
+         */
+        public static final int ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT = 401;
+
+        /** Indicates that the middleware has no record of the supplied {@link DownloadRequest}. */
+        public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402;
+    }
 
     private final int mErrorCode;
 
-    /** @hide
-     * TODO: future systemapi
-     */
+    /** @hide */
     public MbmsException(int errorCode) {
         super();
         mErrorCode = errorCode;
diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
index 27d9878..f67d6e4 100644
--- a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
@@ -16,20 +16,24 @@
 
 package android.telephony.mbms;
 
+import android.content.Context;
+import android.os.RemoteException;
+
 import java.util.List;
 
 /**
- * A Parcelable class with Cell-Broadcast service information.
- * @hide
+ * A callback class that is used to receive information from the middleware on MBMS streaming
+ * services. An instance of this object should be passed into
+ * {@link android.telephony.MbmsStreamingManager#create(Context, MbmsStreamingManagerCallback)}.
  */
 public class MbmsStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
-
-    public final static int ERROR_CARRIER_NOT_SUPPORTED      = 1;
-    public final static int ERROR_UNABLE_TO_INITIALIZE       = 2;
-    public final static int ERROR_UNABLE_TO_ALLOCATE_MEMORY  = 3;
-
-
-    public void error(int errorCode, String message) {
+    /**
+     * Called by the middleware when it has detected an error condition. The possible error codes
+     * are listed in {@link MbmsException}.
+     * @param errorCode The error code.
+     * @param message A human-readable message generated by the middleware for debugging purposes.
+     */
+    public void error(int errorCode, String message) throws RemoteException {
         // default implementation empty
     }
 
@@ -45,20 +49,8 @@
      * @param services a List of StreamingServiceInfos
      *
      */
-    public void streamingServicesUpdated(List<StreamingServiceInfo> services) {
-        // default implementation empty
-    }
-
-    /**
-     * Called to indicate the active Streaming Services have changed.
-     *
-     * This will be caused whenever a new service starts streaming or whenever
-     * MbmsStreamServiceManager.getActiveStreamingServices is called.
-     *
-     * @param services a list of StreamingServiceInfos.  May be empty if
-     *                 there are no active StreamingServices
-     */
-    public void activeStreamingServicesUpdated(List<StreamingServiceInfo> services) {
+    public void streamingServicesUpdated(List<StreamingServiceInfo> services)
+            throws RemoteException {
         // default implementation empty
     }
 
@@ -68,10 +60,10 @@
      * 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#ERROR_MIDDLEWARE_NOT_YET_READY}
+     * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
      */
     @Override
-    public void middlewareReady() {
+    public void middlewareReady() throws RemoteException {
         // default implementation empty
     }
 }
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index 7d47275..4b913f8 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -22,14 +22,11 @@
 import android.content.ServiceConnection;
 import android.content.pm.*;
 import android.content.pm.ServiceInfo;
-import android.telephony.MbmsDownloadManager;
 import android.util.Log;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 /**
  * @hide
@@ -78,11 +75,20 @@
                 MbmsUtils.getMiddlewareServiceInfo(context, serviceAction);
 
         if (mbmsServiceInfo == null) {
-            throw new MbmsException(MbmsException.ERROR_NO_SERVICE_INSTALLED);
+            throw new MbmsException(MbmsException.ERROR_NO_UNIQUE_MIDDLEWARE);
         }
 
         bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo));
 
         context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
     }
+
+    /**
+     * Returns a File linked to the directory used to store temp files for this file service
+     */
+    public static File getEmbmsTempFileDirForService(Context context, String serviceId) {
+        File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
+
+        return new File(embmsTempFileDir, serviceId);
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index b5675b2..e1ccd43 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -26,46 +26,25 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
- * A Parcelable class with Cell-Broadcast service information.
- * @hide
+ * Describes a cell-broadcast service. This class should not be instantiated directly -- use
+ * {@link StreamingServiceInfo} or FileServiceInfo TODO: add link once that's unhidden
  */
 public class ServiceInfo implements Parcelable {
     // arbitrary limit on the number of locale -> name pairs we support
     final static int MAP_LIMIT = 1000;
-    /**
-     * User displayable names listed by language.  Unmodifiable.
-     */
-    final Map<Locale, String> names;
 
-    /**
-     * The class name for this service - used to catagorize and filter
-     */
-    final String className;
+    private final Map<Locale, String> names;
+    private final String className;
+    private final List<Locale> locales;
+    private final String serviceId;
+    private final Date sessionStartTime;
+    private final Date sessionEndTime;
 
-    /**
-     * The languages available for this service content
-     */
-    final List<Locale> locales;
-
-    /**
-     * The carrier's identifier for the service.
-     */
-    final String serviceId;
-
-    /**
-     * The start time indicating when this service will be available.
-     */
-    final Date sessionStartTime;
-
-    /**
-     * The end time indicating when this sesion stops being available.
-     */
-    final Date sessionEndTime;
-
-
+    /** @hide */
     public ServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales,
             String newServiceId, Date start, Date end) {
         if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName)
@@ -88,20 +67,21 @@
         sessionEndTime = (Date)end.clone();
     }
 
-    public static final Parcelable.Creator<FileServiceInfo> CREATOR =
-            new Parcelable.Creator<FileServiceInfo>() {
+    public static final Parcelable.Creator<ServiceInfo> CREATOR =
+            new Parcelable.Creator<ServiceInfo>() {
         @Override
-        public FileServiceInfo createFromParcel(Parcel source) {
-            return new FileServiceInfo(source);
+        public ServiceInfo createFromParcel(Parcel source) {
+            return new ServiceInfo(source);
         }
 
         @Override
-        public FileServiceInfo[] newArray(int size) {
-            return new FileServiceInfo[size];
+        public ServiceInfo[] newArray(int size) {
+            return new ServiceInfo[size];
         }
     };
 
-    ServiceInfo(Parcel in) {
+    /** @hide */
+    protected ServiceInfo(Parcel in) {
         int mapCount = in.readInt();
         if (mapCount > MAP_LIMIT || mapCount < 0) {
             throw new RuntimeException("bad map length" + mapCount);
@@ -151,28 +131,68 @@
         return 0;
     }
 
+    /**
+     * User displayable names listed by language. Do not modify the map returned from this method.
+     */
     public Map<Locale, String> getNames() {
         return names;
     }
 
+    /**
+     * The class name for this service - used to categorize and filter
+     */
     public String getClassName() {
         return className;
     }
 
+    /**
+     * The languages available for this service content
+     */
     public List<Locale> getLocales() {
         return locales;
     }
 
+    /**
+     * The carrier's identifier for the service.
+     */
     public String getServiceId() {
         return serviceId;
     }
 
+    /**
+     * The start time indicating when this service will be available.
+     */
     public Date getSessionStartTime() {
         return sessionStartTime;
     }
 
+    /**
+     * The end time indicating when this session stops being available.
+     */
     public Date getSessionEndTime() {
         return sessionEndTime;
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null) {
+            return false;
+        }
+        if (!(o instanceof ServiceInfo)) {
+            return false;
+        }
+        ServiceInfo that = (ServiceInfo) o;
+        return Objects.equals(names, that.names) &&
+                Objects.equals(className, that.className) &&
+                Objects.equals(locales, that.locales) &&
+                Objects.equals(serviceId, that.serviceId) &&
+                Objects.equals(sessionStartTime, that.sessionStartTime) &&
+                Objects.equals(sessionEndTime, that.sessionEndTime);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(names, className, locales, serviceId, sessionStartTime, sessionEndTime);
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java
index 608d3a9..42c78c3 100644
--- a/telephony/java/android/telephony/mbms/StreamingService.java
+++ b/telephony/java/android/telephony/mbms/StreamingService.java
@@ -16,33 +16,90 @@
 
 package android.telephony.mbms;
 
+import android.annotation.IntDef;
 import android.net.Uri;
-import android.os.DeadObjectException;
 import android.os.RemoteException;
 import android.telephony.mbms.vendor.IMbmsStreamingService;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
- * @hide
+ * Class used to represent a single MBMS stream. After a stream has been started with
+ * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+ * StreamingServiceCallback)},
+ * this class is used to hold information about the stream and control it.
  */
 public class StreamingService {
     private static final String LOG_TAG = "MbmsStreamingService";
 
     /**
      * The state of a stream, reported via {@link StreamingServiceCallback#streamStateUpdated}
+     * @hide
      */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATE_STOPPED, STATE_STARTED, STATE_STALLED})
+    public @interface StreamingState {}
     public final static int STATE_STOPPED = 1;
     public final static int STATE_STARTED = 2;
     public final static int STATE_STALLED = 3;
 
     /**
+     * The reason for a stream state change, reported via
+     * {@link StreamingServiceCallback#streamStateUpdated}
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({REASON_BY_USER_REQUEST, REASON_END_OF_SESSION, REASON_FREQUENCY_CONFLICT,
+            REASON_OUT_OF_MEMORY, REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE,
+            REASON_LEFT_MBMS_BROADCAST_AREA, REASON_NONE})
+    public @interface StreamingStateChangeReason {}
+
+    /**
+     * Indicates that the middleware does not have a reason to provide for the state change.
+     */
+    public static final int REASON_NONE = 0;
+
+    /**
+     * State changed due to a call to {@link #stopStreaming()} or
+     * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+     * StreamingServiceCallback)}
+     */
+    public static final int REASON_BY_USER_REQUEST = 1;
+
+    /**
+     * State changed due to the streaming session ending at the carrier.
+     */
+    public static final int REASON_END_OF_SESSION = 2;
+
+    /**
+     * State changed due to a frequency conflict with another requested stream.
+     */
+    public static final int REASON_FREQUENCY_CONFLICT = 3;
+
+    /**
+     * State changed due to the middleware running out of memory
+     */
+    public static final int REASON_OUT_OF_MEMORY = 4;
+
+    /**
+     * State changed due to the device leaving the home carrier's LTE network.
+     */
+    public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5;
+
+    /**
+     * State changed due to the device leaving the where this stream is being broadcast.
+     */
+    public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 5;
+
+    /**
      * The method of transmission currently used for a stream,
      * reported via {@link StreamingServiceCallback#streamMethodUpdated}
      */
     public final static int BROADCAST_METHOD = 1;
     public final static int UNICAST_METHOD   = 2;
 
-    private final String mAppName;
     private final int mSubscriptionId;
     private final StreamingServiceInfo mServiceInfo;
     private final IStreamingServiceCallback mCallback;
@@ -51,12 +108,10 @@
     /**
      * @hide
      */
-    public StreamingService(String appName,
-            int subscriptionId,
+    public StreamingService(int subscriptionId,
             IMbmsStreamingService service,
             StreamingServiceInfo streamingServiceInfo,
             IStreamingServiceCallback callback) {
-        mAppName = appName;
         mSubscriptionId = subscriptionId;
         mService = service;
         mServiceInfo = streamingServiceInfo;
@@ -67,7 +122,9 @@
      * Retreive the Uri used to play this stream.
      *
      * This may throw a {@link MbmsException} with the error code
-     * {@link MbmsException#ERROR_SERVICE_LOST}
+     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+     *
+     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
      * @return The {@link Uri} to pass to the streaming client.
      */
@@ -77,11 +134,11 @@
         }
 
         try {
-            return mService.getPlaybackUri(mAppName, mSubscriptionId, mServiceInfo.getServiceId());
+            return mService.getPlaybackUri(mSubscriptionId, mServiceInfo.getServiceId());
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService = null;
-            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
         }
     }
 
@@ -95,7 +152,9 @@
     /**
      * Stop streaming this service.
      * This may throw a {@link MbmsException} with the error code
-     * {@link MbmsException#ERROR_SERVICE_LOST}
+     * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+     *
+     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      */
     public void stopStreaming() throws MbmsException {
         if (mService == null) {
@@ -103,25 +162,36 @@
         }
 
         try {
-            mService.stopStreaming(mAppName, mSubscriptionId, mServiceInfo.getServiceId());
+            mService.stopStreaming(mSubscriptionId, mServiceInfo.getServiceId());
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService = null;
-            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
         }
     }
 
+    /**
+     * 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");
         }
 
         try {
-            mService.disposeStream(mAppName, mSubscriptionId, mServiceInfo.getServiceId());
+            mService.disposeStream(mSubscriptionId, mServiceInfo.getServiceId());
         } 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;
-            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
         }
     }
 }
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
index fdfb188..6a1ff9c 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
@@ -16,9 +16,11 @@
 
 package android.telephony.mbms;
 
+import android.os.RemoteException;
+
 /**
- * A Callback class for use when the application is actively streaming content.
- * @hide
+ * A callback class for use when the application is actively streaming content. The middleware
+ * will provide updates on the status of the stream via this callback.
  */
 public class StreamingServiceCallback extends IStreamingServiceCallback.Stub {
 
@@ -31,7 +33,14 @@
      */
     public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1;
 
-    public void error(int errorCode, String message) {
+    /**
+     * Called by the middleware when it has detected an error condition in this stream. The
+     * possible error codes are listed in {@link MbmsException}.
+     * @param errorCode The error code.
+     * @param message A human-readable message generated by the middleware for debugging purposes.
+     */
+    @Override
+    public void error(int errorCode, String message) throws RemoteException {
         // default implementation empty
     }
 
@@ -41,7 +50,9 @@
      * See {@link StreamingService#STATE_STOPPED}, {@link StreamingService#STATE_STARTED}
      * and {@link StreamingService#STATE_STALLED}.
      */
-    public void streamStateUpdated(int state) {
+    @Override
+    public void streamStateUpdated(@StreamingService.StreamingState int state,
+            @StreamingService.StreamingStateChangeReason int reason) throws RemoteException {
         // default implementation empty
     }
 
@@ -55,7 +66,8 @@
      * This may be called when a looping stream hits the end or
      * when parameters have changed to account for time drift.
      */
-    public void mediaDescriptionUpdated() {
+    @Override
+    public void mediaDescriptionUpdated() throws RemoteException {
         // default implementation empty
     }
 
@@ -69,7 +81,8 @@
      * {@link #SIGNAL_STRENGTH_UNAVAILABLE} if broadcast is not available
      * for this service due to timing, geography or popularity.
      */
-    public void broadcastSignalStrengthUpdated(int signalStrength) {
+    @Override
+    public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
         // default implementation empty
     }
 
@@ -89,7 +102,8 @@
      * See {@link StreamingService#BROADCAST_METHOD} and
      * {@link StreamingService#UNICAST_METHOD}
      */
-    public void streamMethodUpdated(int methodType) {
+    @Override
+    public void streamMethodUpdated(int methodType) throws RemoteException {
         // default implementation empty
     }
 }
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
index 77ce3bb..58df24d 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
@@ -16,6 +16,7 @@
 
 package android.telephony.mbms;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -25,15 +26,24 @@
 import java.util.Map;
 
 /**
- * A Parcelable class Cell-Broadcast media stream information.
- * This may not have any more info than ServiceInfo, but kept for completeness.
- * @hide
+ * Describes a single MBMS streaming service.
  */
 public class StreamingServiceInfo extends ServiceInfo implements Parcelable {
 
-    public StreamingServiceInfo(Map<Locale, String> newNames, String newClassName,
-            List<Locale> newLocales, String newServiceId, Date start, Date end) {
-        super(newNames, newClassName, newLocales, newServiceId, start, end);
+    /**
+     * @param names User displayable names listed by language.
+     * @param className The class name for this service - used by frontend apps to categorize and
+     *                  filter.
+     * @param locales The languages available for this service content.
+     * @param serviceId The carrier's identifier for the service.
+     * @param start The start time indicating when this service will be available.
+     * @param end The end time indicating when this session stops being available.
+     * @hide
+     */
+    @SystemApi
+    public StreamingServiceInfo(Map<Locale, String> names, String className,
+            List<Locale> locales, String serviceId, Date start, Date end) {
+        super(names, className, locales, serviceId, start, end);
     }
 
     public static final Parcelable.Creator<StreamingServiceInfo> CREATOR =
@@ -49,7 +59,7 @@
         }
     };
 
-    StreamingServiceInfo(Parcel in) {
+    private StreamingServiceInfo(Parcel in) {
         super(in);
     }
 
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
index ff7d233..dfcc5f7 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
@@ -19,56 +19,30 @@
 import android.app.PendingIntent;
 import android.net.Uri;
 import android.telephony.mbms.DownloadRequest;
-import android.telephony.mbms.DownloadStatus;
+import android.telephony.mbms.FileInfo;
 import android.telephony.mbms.IMbmsDownloadManagerCallback;
-import android.telephony.mbms.IDownloadCallback;
+import android.telephony.mbms.IDownloadProgressListener;
 
 /**
- * The interface the opaque MbmsStreamingService will satisfy.
  * @hide
  */
 interface IMbmsDownloadService
 {
-    /**
-     * Initialize download service
-     * Registers this listener, subId with this appName
-     *
-     * No return value.  Async errors may be reported, but none expected (not doing anything yet).
-     */
-    void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener);
+    int initialize(int subId, IMbmsDownloadManagerCallback listener);
 
-    /**
-     * - Registers serviceClasses of interest with the uid/appName/subId key.
-     * - Starts asynch fetching data on download services of matching classes to be reported
-     * later by callback.
-     *
-     * Note that subsequent calls with the same callback, appName, subId and uid will replace
-     * the service class list.
-     */
-    int getFileServices(String appName, int subId, in List<String> serviceClasses);
+    int getFileServices(int subId, in List<String> serviceClasses);
 
-    int setTempFileRootDirectory(String appName, int subId, String rootDirectoryPath);
-    /**
-     * should move the params into a DownloadRequest parcelable
-     */
-    int download(in DownloadRequest downloadRequest, IDownloadCallback listener);
+    int setTempFileRootDirectory(int subId, String rootDirectoryPath);
 
-    List<DownloadRequest> listPendingDownloads(String appName, int subscriptionId);
+    int download(in DownloadRequest downloadRequest, IDownloadProgressListener listener);
+
+    List<DownloadRequest> listPendingDownloads(int subscriptionId);
 
     int cancelDownload(in DownloadRequest downloadRequest);
 
-    DownloadStatus getDownloadStatus(in DownloadRequest downloadRequest);
+    int getDownloadStatus(in DownloadRequest downloadRequest, in FileInfo fileInfo);
 
-    /*
-     * named this for 2 reasons:
-     *  1 don't want 'State' here as it conflicts with 'Status' of the previous function
-     *  2 want to perfect typing 'Knowledge'
-     */
-    void resetDownloadKnowledge(in DownloadRequest downloadRequest);
+    int resetDownloadKnowledge(in DownloadRequest downloadRequest);
 
-    /**
-     * End of life for this MbmsDownloadManager.
-     * Any pending downloads remain in affect and may start up independently in the future.
-     */
-    void dispose(String appName, int subId);
+    void dispose(int subId);
 }
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
index 8ff7fa7..4dd4292 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
@@ -22,33 +22,22 @@
 import android.telephony.mbms.StreamingServiceInfo;
 
 /**
- * The interface the opaque MbmsStreamingService will satisfy.
  * @hide
  */
 interface IMbmsStreamingService
 {
-    int initialize(IMbmsStreamingManagerCallback listener, String appName, int subId);
+    int initialize(IMbmsStreamingManagerCallback listener, int subId);
 
-    int getStreamingServices(String appName, int subId, in List<String> serviceClasses);
+    int getStreamingServices(int subId, in List<String> serviceClasses);
 
-    int startStreaming(String appName, int subId, String serviceId,
+    int startStreaming(int subId, String serviceId,
             IStreamingServiceCallback listener);
 
-    /**
-     * Per-stream api.  Note each specifies what stream they apply to.
-     */
+    Uri getPlaybackUri(int subId, String serviceId);
 
-    Uri getPlaybackUri(String appName, int subId, String serviceId);
+    void stopStreaming(int subId, String serviceId);
 
-    void stopStreaming(String appName, int subId, String serviceId);
+    void disposeStream(int subId, String serviceId);
 
-    void disposeStream(String appName, int subId, String serviceId);
-
-    /**
-     * End of life for all MbmsStreamingManager's created by this uid/appName/subId.
-     * Ends any streams run under this uid/appname/subId and calls the disposed methods
-     * an callbacks registered for this uid/appName/subId and the disposed methods on any
-     * listeners registered with startStreaming.
-     */
-    void dispose(String appName, int subId);
+    void dispose(int subId);
 }
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 9577dd2..edd5858 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -16,11 +16,13 @@
 
 package android.telephony.mbms.vendor;
 
+import android.annotation.NonNull;
 import android.os.RemoteException;
 import android.telephony.mbms.DownloadRequest;
-import android.telephony.mbms.DownloadStatus;
-import android.telephony.mbms.IDownloadCallback;
+import android.telephony.mbms.FileInfo;
+import android.telephony.mbms.IDownloadProgressListener;
 import android.telephony.mbms.IMbmsDownloadManagerCallback;
+import android.telephony.mbms.MbmsException;
 
 import java.util.List;
 
@@ -31,52 +33,171 @@
  * TODO: future systemapi
  */
 public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
+    /**
+     * Initialize the download service for this app and subId, registering the listener.
+     *
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which
+     * will be intercepted and passed to the app as
+     * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
+     *
+     * 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 IMbmsDownloadManagerCallback#error(int, String)}.
+     *
+     * @param listener The callback to use to communicate with the app.
+     * @param subscriptionId The subscription ID to use.
+     */
     @Override
-    public void initialize(String appName, int subscriptionId,
+    public int initialize(int subscriptionId,
             IMbmsDownloadManagerCallback listener) throws RemoteException {
+        return 0;
     }
 
+    /**
+     * 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 IMbmsDownloadManagerCallback#fileServicesUpdated(List)}
+     *
+     * Note that subsequent calls with the same uid and subId will replace
+     * the service class list.
+     *
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+     *
+     * @param subscriptionId The subscription id to use.
+     * @param serviceClasses The service classes that the app wishes to get info on. The strings
+     *                       may contain arbitrary data as negotiated between the app and the
+     *                       carrier.
+     * @return One of {@link MbmsException#SUCCESS} or
+     *         {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
+     */
     @Override
-    public int getFileServices(String appName, int subscriptionId, List<String> serviceClasses)
+    public int getFileServices(int subscriptionId, List<String> serviceClasses)
             throws RemoteException {
         return 0;
     }
 
+    /**
+     * Sets the temp file root directory for this app/subscriptionId combination. The middleware
+     * should persist {@code rootDirectoryPath} and send it back when sending intents to the
+     * app's {@link android.telephony.mbms.MbmsDownloadReceiver}.
+     *
+     * If the calling app (as identified by the calling UID) currently has any pending download
+     * requests that have not been canceled, the middleware must return
+     * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
+     *
+     * @param subscriptionId The subscription id the download is operating under.
+     * @param rootDirectoryPath The path to the app's temp file root directory.
+     * @return {@link MbmsException#SUCCESS},
+     *         {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
+     *         {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
+     */
     @Override
-    public int setTempFileRootDirectory(String appName, int subscriptionId,
+    public int setTempFileRootDirectory(int subscriptionId,
             String rootDirectoryPath) throws RemoteException {
         return 0;
     }
 
+    /**
+     * Issues a request to download a set of files.
+     *
+     * The middleware should expect that {@link #setTempFileRootDirectory(int, String)} has been
+     * called for this app between when the app was installed and when this method is called. If
+     * this is not the case, an {@link IllegalStateException} may be thrown.
+     *
+     * @param downloadRequest An object describing the set of files to be downloaded.
+     * @param listener A listener through which the middleware can provide progress updates to
+     *                 the app while both are still running.
+     * @return TODO: enumerate possible return values
+     */
     @Override
-    public int download(DownloadRequest downloadRequest, IDownloadCallback listener)
+    public int download(DownloadRequest downloadRequest, IDownloadProgressListener listener)
             throws RemoteException {
         return 0;
     }
 
+
+    /**
+     * Returns a list of pending {@link DownloadRequest}s that originated from the calling
+     * application, identified by its uid. A pending request is one that was issued via
+     * {@link #download(DownloadRequest, IDownloadCallback)} but not cancelled through
+     * {@link #cancelDownload(DownloadRequest)}.
+     * The middleware must return a non-null result synchronously or throw an exception
+     * inheriting from {@link RuntimeException}.
+     * @return A list, possibly empty, of {@link DownloadRequest}s
+     */
     @Override
-    public List<DownloadRequest> listPendingDownloads(String appName, int subscriptionId)
+    public @NonNull List<DownloadRequest> listPendingDownloads(int subscriptionId)
             throws RemoteException {
         return null;
     }
 
+    /**
+     * Issues a request to cancel the specified download request.
+     *
+     * If the middleware is unable to cancel the request for whatever reason, it should return
+     * synchronously with an error. If this method returns {@link MbmsException#SUCCESS}, the app
+     * will no longer be expecting any more file-completed intents from the middleware for this
+     * {@link DownloadRequest}.
+     * @param downloadRequest The request to cancel
+     * @return {@link MbmsException#SUCCESS},
+     *         {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
+     *         {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+     */
     @Override
     public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException {
         return 0;
     }
 
+    /**
+     * Gets information about the status of a file pending download.
+     *
+     * If the middleware has not yet been properly initialized or if it has no records of the
+     * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
+     * {@link android.telephony.MbmsDownloadManager#STATUS_UNKNOWN} must be returned.
+     *
+     * @param downloadRequest The download request to query.
+     * @param fileInfo The particular file within the request to get information on.
+     * @return The status of the download.
+     */
     @Override
-    public DownloadStatus getDownloadStatus(DownloadRequest downloadRequest)
+    public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo)
             throws RemoteException {
-        return null;
+        return 0;
     }
 
+    /**
+     * Resets the middleware's knowledge of previously-downloaded files in this download request.
+     *
+     * When this method is called, the middleware must attempt to re-download all the files
+     * specified by the {@link DownloadRequest}, even if the files have not changed on the server.
+     * In addition, current in-progress downloads must not be interrupted.
+     *
+     * If the middleware is not aware of the specified download request, return
+     * {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
+     *
+     * @param downloadRequest The request to re-download files for.
+     */
     @Override
-    public void resetDownloadKnowledge(DownloadRequest downloadRequest)
+    public int resetDownloadKnowledge(DownloadRequest downloadRequest)
             throws RemoteException {
+        return 0;
     }
 
+    /**
+     * 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(int, IMbmsDownloadManagerCallback)} should no longer be used
+     * after this method has been called by the app.
+     *
+     * Any download requests issued by the app should remain in effect until the app calls
+     * {@link #cancelDownload(DownloadRequest)} on another session.
+     *
+     * May throw an {@link IllegalStateException}
+     *
+     * @param subscriptionId The subscription id to use.
+     */
     @Override
-    public void dispose(String appName, int subscriptionId) throws RemoteException {
+    public void dispose(int subscriptionId) throws RemoteException {
     }
 }
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 2c1f085..b2200c3 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -17,56 +17,89 @@
 package android.telephony.mbms.vendor;
 
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.telephony.mbms.IMbmsStreamingManagerCallback;
 import android.telephony.mbms.IStreamingServiceCallback;
 import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.StreamingService;
+import android.telephony.mbms.StreamingServiceCallback;
+import android.telephony.mbms.StreamingServiceInfo;
 
 import java.util.List;
 
 /**
  * @hide
- * TODO: future systemapi
  */
+@SystemApi
 public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
     /**
      * Initialize streaming service for this app and subId, registering the listener.
      *
-     * May throw an {@link IllegalArgumentException} or a {@link SecurityException}
+     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which
+     * will be intercepted and passed to the app as
+     * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
+     *
+     * 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)}.
      *
      * @param listener The callback to use to communicate with the app.
-     * @param appName The app name as negotiated with the wireless carrier.
      * @param subscriptionId The subscription ID to use.
-     * @return {@link MbmsException#SUCCESS} or {@link MbmsException#ERROR_ALREADY_INITIALIZED}
      */
-    @Override
-    public int initialize(IMbmsStreamingManagerCallback listener, String appName,
-            int subscriptionId) throws RemoteException {
+    public int initialize(MbmsStreamingManagerCallback listener, int subscriptionId)
+            throws RemoteException {
         return 0;
     }
 
     /**
+     * Actual AIDL implementation that hides the callback AIDL from the middleware.
+     * @hide
+     */
+    @Override
+    public final int initialize(IMbmsStreamingManagerCallback listener, int subscriptionId)
+            throws RemoteException {
+        return initialize(new MbmsStreamingManagerCallback() {
+            @Override
+            public void error(int errorCode, String message) throws RemoteException {
+                listener.error(errorCode, message);
+            }
+
+            @Override
+            public void streamingServicesUpdated(List<StreamingServiceInfo> services) throws
+                    RemoteException {
+                listener.streamingServicesUpdated(services);
+            }
+
+            @Override
+            public void middlewareReady() throws RemoteException {
+                listener.middlewareReady();
+            }
+        }, subscriptionId);
+    }
+
+
+    /**
      * Registers serviceClasses of interest with the appName/subId key.
      * Starts async fetching data on streaming services of matching classes to be reported
      * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)}
      *
-     * Note that subsequent calls with the same uid, appName and subId will replace
+     * Note that subsequent calls with the same uid and subId will replace
      * the service class list.
      *
      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
-     * @param appName The app name as negotiated with the wireless carrier.
      * @param subscriptionId The subscription id to use.
      * @param serviceClasses The service classes that the app wishes to get info on. The strings
      *                       may contain arbitrary data as negotiated between the app and the
      *                       carrier.
-     * @return One of {@link MbmsException#SUCCESS},
-     *         {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND},
-     *         {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
+     * @return {@link MbmsException#SUCCESS} or any of the errors in
+     * {@link android.telephony.mbms.MbmsException.GeneralErrors}
      */
     @Override
-    public int getStreamingServices(String appName, int subscriptionId,
+    public int getStreamingServices(int subscriptionId,
             List<String> serviceClasses) throws RemoteException {
         return 0;
     }
@@ -74,21 +107,56 @@
     /**
      * 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#streamStateChanged(int)}.
+     * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
      *
      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
-     * @param appName The app name as negotiated with the wireless carrier.
      * @param subscriptionId The subscription id to use.
      * @param serviceId The ID of the streaming service that the app has requested.
      * @param listener The listener object on which the app wishes to receive updates.
-     * @return {@link MbmsException#SUCCESS}, {@link MbmsException#ERROR_STREAM_ALREADY_STARTED},
-     *         or {@link MbmsException#ERROR_UNABLE_TO_START_SERVICE}.
+     * @return Any error in {@link android.telephony.mbms.MbmsException.GeneralErrors}
+     */
+    public int startStreaming(int subscriptionId, String serviceId,
+            StreamingServiceCallback listener) throws RemoteException {
+        return 0;
+    }
+
+    /**
+     * Actual AIDL implementation of startStreaming that hides the callback AIDL from the
+     * middleware.
+     * @hide
      */
     @Override
-    public int startStreaming(String appName, int subscriptionId,
-            String serviceId, IStreamingServiceCallback listener) throws RemoteException {
-        return 0;
+    public int startStreaming(int subscriptionId, String serviceId,
+            IStreamingServiceCallback listener) throws RemoteException {
+        return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
+            @Override
+            public void error(int errorCode, String message) throws RemoteException {
+                listener.error(errorCode, message);
+            }
+
+            @Override
+            public void streamStateUpdated(@StreamingService.StreamingState int state,
+                    @StreamingService.StreamingStateChangeReason int reason)
+                    throws RemoteException {
+                listener.streamStateUpdated(state, reason);
+            }
+
+            @Override
+            public void mediaDescriptionUpdated() throws RemoteException {
+                listener.mediaDescriptionUpdated();
+            }
+
+            @Override
+            public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
+                listener.broadcastSignalStrengthUpdated(signalStrength);
+            }
+
+            @Override
+            public void streamMethodUpdated(int methodType) throws RemoteException {
+                listener.streamMethodUpdated(methodType);
+            }
+        });
     }
 
     /**
@@ -97,13 +165,12 @@
      *
      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
-     * @param appName The app name as negotiated with the wireless carrier.
      * @param subscriptionId The subscription id to use.
      * @param serviceId The ID of the streaming service that the app has requested.
      * @return An opaque {@link Uri} to be passed to a video player that understands the format.
      */
     @Override
-    public @Nullable Uri getPlaybackUri(String appName, int subscriptionId, String serviceId)
+    public @Nullable Uri getPlaybackUri(int subscriptionId, String serviceId)
             throws RemoteException {
         return null;
     }
@@ -111,16 +178,15 @@
     /**
      * Stop streaming the stream identified by {@code serviceId}. Notification of the resulting
      * stream state change should be reported to the app via
-     * {@link IStreamingServiceCallback#streamStateChanged(int)}.
+     * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
      *
      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
-     * @param appName The app name as negotiated with the wireless carrier.
      * @param subscriptionId The subscription id to use.
      * @param serviceId The ID of the streaming service that the app wishes to stop.
      */
     @Override
-    public void stopStreaming(String appName, int subscriptionId, String serviceId)
+    public void stopStreaming(int subscriptionId, String serviceId)
             throws RemoteException {
     }
 
@@ -128,33 +194,31 @@
      * 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(String, int, String, IStreamingServiceCallback)} should no longer be
+     * {@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 appName The app name as negotiated with the wireless carrier.
      * @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(String appName, int subscriptionId, String serviceId)
+    public void disposeStream(int subscriptionId, String serviceId)
             throws RemoteException {
     }
 
     /**
-     * Signals that the app wishes to dispose of the session identified by the {@code appName} and
-     * {@code subscriptionId} arguments, as well as the caller's uid. No notification back to the
+     * 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, String, int)} should no longer be used
+     * {@link #initialize(IMbmsStreamingManagerCallback, int)} should no longer be used
      * after this method has been called by the app.
      *
      * May throw an {@link IllegalStateException}
      *
-     * @param appName The app name as negotiated with the wireless carrier.
      * @param subscriptionId The subscription id to use.
      */
     @Override
-    public void dispose(String appName, int subscriptionId) throws RemoteException {
+    public void dispose(int subscriptionId) throws RemoteException {
     }
 }
diff --git a/telephony/java/com/android/internal/telephony/NetworkScanResult.java b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
index df80666..8b09848 100644
--- a/telephony/java/com/android/internal/telephony/NetworkScanResult.java
+++ b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
@@ -36,10 +36,10 @@
 public final class NetworkScanResult implements Parcelable {
 
     // Contains only part of the scan result and more are coming.
-    public static final int SCAN_STATUS_PARTIAL = 0;
+    public static final int SCAN_STATUS_PARTIAL = 1;
 
     // Contains the last part of the scan result and the scan is now complete.
-    public static final int SCAN_STATUS_COMPLETE = 1;
+    public static final int SCAN_STATUS_COMPLETE = 2;
 
     // The status of the scan, only valid when scanError = SUCCESS.
     public int scanStatus;
diff --git a/test-runner/src/android/test/ClassPathPackageInfo.java b/test-runner/src/android/test/ClassPathPackageInfo.java
deleted file mode 100644
index 2cf76af..0000000
--- a/test-runner/src/android/test/ClassPathPackageInfo.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * The Package object doesn't allow you to iterate over the contained
- * classes and subpackages of that package.  This is a version that does.
- *
- * {@hide} Not needed for 1.0 SDK.
- */
-@Deprecated
-public class ClassPathPackageInfo {
-
-    private final ClassPathPackageInfoSource source;
-    private final String packageName;
-    private final Set<String> subpackageNames;
-    private final Set<Class<?>> topLevelClasses;
-
-    ClassPathPackageInfo(ClassPathPackageInfoSource source, String packageName,
-            Set<String> subpackageNames, Set<Class<?>> topLevelClasses) {
-        this.source = source;
-        this.packageName = packageName;
-        this.subpackageNames = Collections.unmodifiableSet(subpackageNames);
-        this.topLevelClasses = Collections.unmodifiableSet(topLevelClasses);
-    }
-
-    public Set<ClassPathPackageInfo> getSubpackages() {
-        Set<ClassPathPackageInfo> info = new HashSet<>();
-        for (String name : subpackageNames) {
-            info.add(source.getPackageInfo(name));
-        }
-        return info;
-    }
-
-    public Set<Class<?>> getTopLevelClassesRecursive() {
-        Set<Class<?>> set = new HashSet<>();
-        addTopLevelClassesTo(set);
-        return set;
-    }
-
-    private void addTopLevelClassesTo(Set<Class<?>> set) {
-        set.addAll(topLevelClasses);
-        for (ClassPathPackageInfo info : getSubpackages()) {
-            info.addTopLevelClassesTo(set);
-        }
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj instanceof ClassPathPackageInfo) {
-            ClassPathPackageInfo that = (ClassPathPackageInfo) obj;
-            return (this.packageName).equals(that.packageName);
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return packageName.hashCode();
-    }
-}
diff --git a/test-runner/src/android/test/ClassPathPackageInfoSource.java b/test-runner/src/android/test/ClassPathPackageInfoSource.java
index 9bcc25a..755b540 100644
--- a/test-runner/src/android/test/ClassPathPackageInfoSource.java
+++ b/test-runner/src/android/test/ClassPathPackageInfoSource.java
@@ -21,15 +21,12 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Enumeration;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.regex.Pattern;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 
 /**
  * Generate {@link ClassPathPackageInfo}s by scanning apk paths.
@@ -39,11 +36,13 @@
 @Deprecated
 public class ClassPathPackageInfoSource {
 
-    private static final String CLASS_EXTENSION = ".class";
-
     private static final ClassLoader CLASS_LOADER
             = ClassPathPackageInfoSource.class.getClassLoader();
 
+    private static String[] apkPaths;
+
+    private static ClassPathPackageInfoSource classPathSource;
+
     private final SimpleCache<String, ClassPathPackageInfo> cache =
             new SimpleCache<String, ClassPathPackageInfo>() {
                 @Override
@@ -54,23 +53,28 @@
 
     // The class path of the running application
     private final String[] classPath;
-    private static String[] apkPaths;
 
-    // A cache of jar file contents
-    private final Map<File, Set<String>> jarFiles = new HashMap<>();
-    private ClassLoader classLoader;
+    private final ClassLoader classLoader;
 
-    ClassPathPackageInfoSource() {
+    private ClassPathPackageInfoSource(ClassLoader classLoader) {
+        this.classLoader = classLoader;
         classPath = getClassPath();
     }
 
-
-    public static void setApkPaths(String[] apkPaths) {
+    static void setApkPaths(String[] apkPaths) {
         ClassPathPackageInfoSource.apkPaths = apkPaths;
     }
 
-    public ClassPathPackageInfo getPackageInfo(String pkgName) {
-        return cache.get(pkgName);
+    public static ClassPathPackageInfoSource forClassPath(ClassLoader classLoader) {
+        if (classPathSource == null) {
+            classPathSource = new ClassPathPackageInfoSource(classLoader);
+        }
+        return classPathSource;
+    }
+
+    public Set<Class<?>> getTopLevelClassesRecursive(String packageName) {
+        ClassPathPackageInfo packageInfo = cache.get(packageName);
+        return packageInfo.getTopLevelClassesRecursive();
     }
 
     private ClassPathPackageInfo createPackageInfo(String packageName) {
@@ -96,7 +100,7 @@
                         + "'. Message: " + e.getMessage(), e);
             }
         }
-        return new ClassPathPackageInfo(this, packageName, subpackageNames,
+        return new ClassPathPackageInfo(packageName, subpackageNames,
                 topLevelClasses);
     }
 
@@ -107,9 +111,6 @@
      */
     private void findClasses(String packageName, Set<String> classNames,
             Set<String> subpackageNames) {
-        String packagePrefix = packageName + '.';
-        String pathPrefix = packagePrefix.replace('.', '/');
-
         for (String entryName : classPath) {
             File classPathEntry = new File(entryName);
 
@@ -150,58 +151,6 @@
 
     /**
      * Finds all classes and sub packages that are below the packageName and
-     * add them to the respective sets. Searches the package in a class directory.
-     */
-    private void findClassesInDirectory(File classDir,
-            String packagePrefix, String pathPrefix, Set<String> classNames,
-            Set<String> subpackageNames)
-            throws IOException {
-        File directory = new File(classDir, pathPrefix);
-
-        if (directory.exists()) {
-            for (File f : directory.listFiles()) {
-                String name = f.getName();
-                if (name.endsWith(CLASS_EXTENSION) && isToplevelClass(name)) {
-                    classNames.add(packagePrefix + getClassName(name));
-                } else if (f.isDirectory()) {
-                    subpackageNames.add(packagePrefix + name);
-                }
-            }
-        }
-    }
-
-    /**
-     * Finds all classes and sub packages that are below the packageName and
-     * add them to the respective sets. Searches the package in a single jar file.
-     */
-    private void findClassesInJar(File jarFile, String pathPrefix,
-            Set<String> classNames, Set<String> subpackageNames)
-            throws IOException {
-        Set<String> entryNames = getJarEntries(jarFile);
-        // check if the Jar contains the package.
-        if (!entryNames.contains(pathPrefix)) {
-            return;
-        }
-        int prefixLength = pathPrefix.length();
-        for (String entryName : entryNames) {
-            if (entryName.startsWith(pathPrefix)) {
-                if (entryName.endsWith(CLASS_EXTENSION)) {
-                    // check if the class is in the package itself or in one of its
-                    // subpackages.
-                    int index = entryName.indexOf('/', prefixLength);
-                    if (index >= 0) {
-                        String p = entryName.substring(0, index).replace('/', '.');
-                        subpackageNames.add(p);
-                    } else if (isToplevelClass(entryName)) {
-                        classNames.add(getClassName(entryName).replace('/', '.'));
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Finds all classes and sub packages that are below the packageName and
      * add them to the respective sets. Searches the package in a single apk file.
      */
     private void findClassesInApk(String apkPath, String packageName,
@@ -242,47 +191,6 @@
     }
 
     /**
-     * Gets the class and package entries from a Jar.
-     */
-    private Set<String> getJarEntries(File jarFile)
-            throws IOException {
-        Set<String> entryNames = jarFiles.get(jarFile);
-        if (entryNames == null) {
-            entryNames = new HashSet<>();
-            ZipFile zipFile = new ZipFile(jarFile);
-            Enumeration<? extends ZipEntry> entries = zipFile.entries();
-            while (entries.hasMoreElements()) {
-                String entryName = entries.nextElement().getName();
-                if (entryName.endsWith(CLASS_EXTENSION)) {
-                    // add the entry name of the class
-                    entryNames.add(entryName);
-
-                    // add the entry name of the classes package, i.e. the entry name of
-                    // the directory that the class is in. Used to quickly skip jar files
-                    // if they do not contain a certain package.
-                    //
-                    // Also add parent packages so that a JAR that contains
-                    // pkg1/pkg2/Foo.class will be marked as containing pkg1/ in addition
-                    // to pkg1/pkg2/ and pkg1/pkg2/Foo.class.  We're still interested in
-                    // JAR files that contains subpackages of a given package, even if
-                    // an intermediate package contains no direct classes.
-                    //
-                    // Classes in the default package will cause a single package named
-                    // "" to be added instead.
-                    int lastIndex = entryName.lastIndexOf('/');
-                    do {
-                        String packageName = entryName.substring(0, lastIndex + 1);
-                        entryNames.add(packageName);
-                        lastIndex = entryName.lastIndexOf('/', lastIndex - 1);
-                    } while (lastIndex > 0);
-                }
-            }
-            jarFiles.put(jarFile, entryNames);
-        }
-        return entryNames;
-    }
-
-    /**
      * Checks if a given file name represents a toplevel class.
      */
     private static boolean isToplevelClass(String fileName) {
@@ -290,14 +198,6 @@
     }
 
     /**
-     * Given the absolute path of a class file, return the class name.
-     */
-    private static String getClassName(String className) {
-        int classNameEnd = className.length() - CLASS_EXTENSION.length();
-        return className.substring(0, classNameEnd);
-    }
-
-    /**
      * Gets the class path from the System Property "java.class.path" and splits
      * it up into the individual elements.
      */
@@ -307,7 +207,56 @@
         return classPath.split(Pattern.quote(separator));
     }
 
-    public void setClassLoader(ClassLoader classLoader) {
-        this.classLoader = classLoader;
+    /**
+     * The Package object doesn't allow you to iterate over the contained
+     * classes and subpackages of that package.  This is a version that does.
+     */
+    private class ClassPathPackageInfo {
+
+        private final String packageName;
+        private final Set<String> subpackageNames;
+        private final Set<Class<?>> topLevelClasses;
+
+        private ClassPathPackageInfo(String packageName,
+                Set<String> subpackageNames, Set<Class<?>> topLevelClasses) {
+            this.packageName = packageName;
+            this.subpackageNames = Collections.unmodifiableSet(subpackageNames);
+            this.topLevelClasses = Collections.unmodifiableSet(topLevelClasses);
+        }
+
+        private Set<ClassPathPackageInfo> getSubpackages() {
+            Set<ClassPathPackageInfo> info = new HashSet<>();
+            for (String name : subpackageNames) {
+                info.add(cache.get(name));
+            }
+            return info;
+        }
+
+        private Set<Class<?>> getTopLevelClassesRecursive() {
+            Set<Class<?>> set = new HashSet<>();
+            addTopLevelClassesTo(set);
+            return set;
+        }
+
+        private void addTopLevelClassesTo(Set<Class<?>> set) {
+            set.addAll(topLevelClasses);
+            for (ClassPathPackageInfo info : getSubpackages()) {
+                info.addTopLevelClassesTo(set);
+            }
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ClassPathPackageInfo) {
+                ClassPathPackageInfo that = (ClassPathPackageInfo) obj;
+                return (this.packageName).equals(that.packageName);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return packageName.hashCode();
+        }
     }
 }
diff --git a/test-runner/src/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java
index 6e5492b..3f8b7a7 100644
--- a/test-runner/src/android/test/InstrumentationTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationTestRunner.java
@@ -16,8 +16,9 @@
 
 package android.test;
 
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
 import com.android.internal.util.Predicate;
-import com.android.internal.util.Predicates;
 
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -30,7 +31,6 @@
 import android.test.suitebuilder.TestMethod;
 import android.test.suitebuilder.TestPredicates;
 import android.test.suitebuilder.TestSuiteBuilder;
-import android.test.suitebuilder.annotation.HasAnnotation;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
@@ -52,6 +52,8 @@
 import junit.runner.BaseTestRunner;
 import junit.textui.ResultPrinter;
 
+import static android.test.suitebuilder.TestPredicates.hasAnnotation;
+
 /**
  * An {@link Instrumentation} that runs various types of {@link junit.framework.TestCase}s against
  * an Android package (application).
@@ -196,6 +198,12 @@
     /** @hide */
     static final String ARGUMENT_NOT_ANNOTATION = "notAnnotation";
 
+    private static final Predicate<TestMethod> SELECT_SMALL = hasAnnotation(SmallTest.class);
+
+    private static final Predicate<TestMethod> SELECT_MEDIUM = hasAnnotation(MediumTest.class);
+
+    private static final Predicate<TestMethod> SELECT_LARGE = hasAnnotation(LargeTest.class);
+
     /**
      * This constant defines the maximum allowed runtime (in ms) for a test included in the "small"
      * suite. It is used to make an educated guess at what suite an unlabeled test belongs.
@@ -464,11 +472,11 @@
     private Predicate<TestMethod> getSizePredicateFromArg(String sizeArg) {
 
         if (SMALL_SUITE.equals(sizeArg)) {
-            return TestPredicates.SELECT_SMALL;
+            return SELECT_SMALL;
         } else if (MEDIUM_SUITE.equals(sizeArg)) {
-            return TestPredicates.SELECT_MEDIUM;
+            return SELECT_MEDIUM;
         } else if (LARGE_SUITE.equals(sizeArg)) {
-            return TestPredicates.SELECT_LARGE;
+            return SELECT_LARGE;
         } else {
             return null;
         }
@@ -483,7 +491,7 @@
     private Predicate<TestMethod> getAnnotationPredicate(String annotationClassName) {
         Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
         if (annotationClass != null) {
-            return new HasAnnotation(annotationClass);
+            return hasAnnotation(annotationClass);
         }
         return null;
     }
@@ -497,7 +505,7 @@
      private Predicate<TestMethod> getNotAnnotationPredicate(String annotationClassName) {
          Class<? extends Annotation> annotationClass = getAnnotationClass(annotationClassName);
          if (annotationClass != null) {
-             return Predicates.not(new HasAnnotation(annotationClass));
+             return TestPredicates.not(hasAnnotation(annotationClass));
          }
          return null;
      }
diff --git a/test-runner/src/android/test/PackageInfoSources.java b/test-runner/src/android/test/PackageInfoSources.java
deleted file mode 100644
index 205f86b..0000000
--- a/test-runner/src/android/test/PackageInfoSources.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test;
-
-/**
- * {@hide} Not needed for SDK.
- */
-@Deprecated
-public class PackageInfoSources {
-
-    private static ClassPathPackageInfoSource classPathSource;
-
-    private PackageInfoSources() {
-    }
-
-    public static ClassPathPackageInfoSource forClassPath(ClassLoader classLoader) {
-        if (classPathSource == null) {
-            classPathSource = new ClassPathPackageInfoSource();
-            classPathSource.setClassLoader(classLoader);
-        }
-        return classPathSource;
-    }
-
-}
diff --git a/test-runner/src/android/test/TestCase.java b/test-runner/src/android/test/TestCase.java
deleted file mode 100644
index b234f44..0000000
--- a/test-runner/src/android/test/TestCase.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2006 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.test;
-
-import android.content.Context;
-
-/**
- * {@hide}
- * More complex interface for test cases.
- *
- * <p>Just implementing Runnable is enough for many test cases.  If you
- * have additional setup or teardown, this interface might be for you,
- * especially if you need to share it between different test cases, or your
- * teardown code must execute regardless of whether your test passed.
- *
- * <p>See the android.test package documentation (click the more... link)
- * for a full description
- */
-@Deprecated
-public interface TestCase extends Runnable
-{
-    /**
-     * Called before run() is called.
-     */
-    public void setUp(Context context);
-
-    /**
-     * Called after run() is called, even if run() threw an exception, but
-     * not if setUp() threw an execption.
-     */
-    public void tearDown();
-}
-
diff --git a/test-runner/src/android/test/suitebuilder/AssignableFrom.java b/test-runner/src/android/test/suitebuilder/AssignableFrom.java
index 38b4ee3..84db066 100644
--- a/test-runner/src/android/test/suitebuilder/AssignableFrom.java
+++ b/test-runner/src/android/test/suitebuilder/AssignableFrom.java
@@ -20,9 +20,9 @@
 
 class AssignableFrom implements Predicate<TestMethod> {
 
-    private final Class root;
+    private final Class<?> root;
 
-    AssignableFrom(Class root) {
+    AssignableFrom(Class<?> root) {
         this.root = root;
     }
 
diff --git a/test-runner/src/android/test/suitebuilder/TestGrouping.java b/test-runner/src/android/test/suitebuilder/TestGrouping.java
index 307afb5..030bc42 100644
--- a/test-runner/src/android/test/suitebuilder/TestGrouping.java
+++ b/test-runner/src/android/test/suitebuilder/TestGrouping.java
@@ -16,9 +16,7 @@
 
 package android.test.suitebuilder;
 
-import android.test.ClassPathPackageInfo;
 import android.test.ClassPathPackageInfoSource;
-import android.test.PackageInfoSources;
 import android.util.Log;
 import com.android.internal.util.Predicate;
 import junit.framework.TestCase;
@@ -131,10 +129,9 @@
     }
 
     private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) {
-        ClassPathPackageInfoSource source = PackageInfoSources.forClassPath(classLoader);
-        ClassPathPackageInfo packageInfo = source.getPackageInfo(packageName);
+        ClassPathPackageInfoSource source = ClassPathPackageInfoSource.forClassPath(classLoader);
 
-        return selectTestClasses(packageInfo.getTopLevelClassesRecursive());
+        return selectTestClasses(source.getTopLevelClassesRecursive(packageName));
     }
 
     @SuppressWarnings("unchecked")
diff --git a/test-runner/src/android/test/suitebuilder/TestPredicates.java b/test-runner/src/android/test/suitebuilder/TestPredicates.java
index 47aca55..616d1a9 100644
--- a/test-runner/src/android/test/suitebuilder/TestPredicates.java
+++ b/test-runner/src/android/test/suitebuilder/TestPredicates.java
@@ -17,30 +17,63 @@
 package android.test.suitebuilder;
 
 import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.HasAnnotation;
-import android.test.suitebuilder.annotation.Suppress;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.test.suitebuilder.annotation.Smoke;
+import android.test.suitebuilder.annotation.Suppress;
 import com.android.internal.util.Predicate;
-import com.android.internal.util.Predicates;
+import java.lang.annotation.Annotation;
 
 /**
  * {@hide} Not needed for 1.0 SDK.
  */
 public class TestPredicates {
 
-    public static final Predicate<TestMethod> SELECT_INSTRUMENTATION =
-            new AssignableFrom(InstrumentationTestCase.class);
-    public static final Predicate<TestMethod> REJECT_INSTRUMENTATION =
-            Predicates.not(SELECT_INSTRUMENTATION);
+    static final Predicate<TestMethod> REJECT_INSTRUMENTATION =
+            not(new AssignableFrom(InstrumentationTestCase.class));
 
-    public static final Predicate<TestMethod> SELECT_SMOKE = new HasAnnotation(Smoke.class);
-    public static final Predicate<TestMethod> SELECT_SMALL = new HasAnnotation(SmallTest.class);
-    public static final Predicate<TestMethod> SELECT_MEDIUM = new HasAnnotation(MediumTest.class);
-    public static final Predicate<TestMethod> SELECT_LARGE = new HasAnnotation(LargeTest.class);
-    public static final Predicate<TestMethod> REJECT_SUPPRESSED =
-            Predicates.not(new HasAnnotation(Suppress.class));
+    static final Predicate<TestMethod> SELECT_SMOKE = hasAnnotation(Smoke.class);
 
+    static final Predicate<TestMethod> REJECT_SUPPRESSED = not(hasAnnotation(Suppress.class));
+
+    /**
+     * Return a predicate that checks to see if a {@link TestMethod} has an instance of the supplied
+     * annotation class, either on the method or on the containing class.
+     */
+    public static Predicate<TestMethod> hasAnnotation(Class<? extends Annotation> annotationClass) {
+        return new HasAnnotation(annotationClass);
+    }
+
+    private static class HasAnnotation implements Predicate<TestMethod> {
+
+        private final Class<? extends Annotation> annotationClass;
+
+        private HasAnnotation(Class<? extends Annotation> annotationClass) {
+            this.annotationClass = annotationClass;
+        }
+
+        @Override
+        public boolean apply(TestMethod testMethod) {
+            return testMethod.getAnnotation(annotationClass) != null ||
+                    testMethod.getEnclosingClass().getAnnotation(annotationClass) != null;
+        }
+    }
+
+    /**
+     * Returns a Predicate that evaluates to true iff the given Predicate
+     * evaluates to false.
+     */
+    public static <T> Predicate<T> not(Predicate<? super T> predicate) {
+        return new NotPredicate<T>(predicate);
+    }
+
+    private static class NotPredicate<T> implements Predicate<T> {
+        private final Predicate<? super T> predicate;
+
+        private NotPredicate(Predicate<? super T> predicate) {
+            this.predicate = predicate;
+        }
+
+        public boolean apply(T t) {
+            return !predicate.apply(t);
+        }
+    }
 }
diff --git a/test-runner/src/android/test/suitebuilder/annotation/HasAnnotation.java b/test-runner/src/android/test/suitebuilder/annotation/HasAnnotation.java
deleted file mode 100644
index a2868fc..0000000
--- a/test-runner/src/android/test/suitebuilder/annotation/HasAnnotation.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.suitebuilder.annotation;
-
-import static com.android.internal.util.Predicates.or;
-import com.android.internal.util.Predicate;
-import android.test.suitebuilder.TestMethod;
-
-import java.lang.annotation.Annotation;
-
-/**
- * A predicate that checks to see if a {@link TestMethod} has a specific annotation, either on the
- * method or on the containing class.
- * 
- * {@hide} Not needed for 1.0 SDK.
- */
-public class HasAnnotation implements Predicate<TestMethod> {
-
-    private Predicate<TestMethod> hasMethodOrClassAnnotation;
-
-    public HasAnnotation(Class<? extends Annotation> annotationClass) {
-        this.hasMethodOrClassAnnotation = or(
-                new HasMethodAnnotation(annotationClass),
-                new HasClassAnnotation(annotationClass));
-    }
-
-    public boolean apply(TestMethod testMethod) {
-        return hasMethodOrClassAnnotation.apply(testMethod);
-    }
-}
diff --git a/test-runner/src/android/test/suitebuilder/annotation/HasClassAnnotation.java b/test-runner/src/android/test/suitebuilder/annotation/HasClassAnnotation.java
deleted file mode 100644
index ac76f4c..0000000
--- a/test-runner/src/android/test/suitebuilder/annotation/HasClassAnnotation.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.suitebuilder.annotation;
-
-import java.lang.annotation.Annotation;
-
-import android.test.suitebuilder.TestMethod;
-import com.android.internal.util.Predicate;
-
-/**
- * A predicate that checks to see if a {@link android.test.suitebuilder.TestMethod} has a specific annotation on the
- * containing class. Consider using the public {@link HasAnnotation} class instead of this class.
- * 
- * {@hide} Not needed for 1.0 SDK.
- */
-class HasClassAnnotation implements Predicate<TestMethod> {
-
-    private Class<? extends Annotation> annotationClass;
-
-    public HasClassAnnotation(Class<? extends Annotation> annotationClass) {
-        this.annotationClass = annotationClass;
-    }
-
-    public boolean apply(TestMethod testMethod) {
-        return testMethod.getEnclosingClass().getAnnotation(annotationClass) != null;
-    }
-}
diff --git a/test-runner/src/android/test/suitebuilder/annotation/HasMethodAnnotation.java b/test-runner/src/android/test/suitebuilder/annotation/HasMethodAnnotation.java
deleted file mode 100644
index 96bd922..0000000
--- a/test-runner/src/android/test/suitebuilder/annotation/HasMethodAnnotation.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.suitebuilder.annotation;
-
-import com.android.internal.util.Predicate;
-import android.test.suitebuilder.TestMethod;
-
-import java.lang.annotation.Annotation;
-
-/**
- * A predicate that checks to see if a the method represented by {@link TestMethod} has a certain
- * annotation on it. Consider using the public {@link HasAnnotation} class instead of this class.
- * 
- * {@hide} Not needed for 1.0 SDK.
- */
-class HasMethodAnnotation implements Predicate<TestMethod> {
-
-    private final Class<? extends Annotation> annotationClass;
-
-    public HasMethodAnnotation(Class<? extends Annotation> annotationClass) {
-        this.annotationClass = annotationClass;
-    }
-
-    public boolean apply(TestMethod testMethod) {
-        return testMethod.getAnnotation(annotationClass) != null;
-    }
-}
diff --git a/test-runner/tests/Android.mk b/test-runner/tests/Android.mk
index cc9b01d..7ee047e4 100644
--- a/test-runner/tests/Android.mk
+++ b/test-runner/tests/Android.mk
@@ -18,7 +18,7 @@
 # We only want this apk build for tests.
 #
 # Run the tests using the following commands:
-#   adb -r install ${ANDROID_PRODUCT_OUT}/data/app/FrameworkTestRunnerTests/FrameworkTestRunnerTests.apk
+#   adb install -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworkTestRunnerTests/FrameworkTestRunnerTests.apk
 #   adb shell am instrument \
         -e notAnnotation android.test.suitebuilder.examples.error.RunAsPartOfSeparateTest \
         -w com.android.frameworks.testrunner.tests/android.test.InstrumentationTestRunner
diff --git a/test-runner/tests/src/android/test/suitebuilder/annotation/HasAnnotationTest.java b/test-runner/tests/src/android/test/suitebuilder/TestPredicatesTest.java
similarity index 76%
rename from test-runner/tests/src/android/test/suitebuilder/annotation/HasAnnotationTest.java
rename to test-runner/tests/src/android/test/suitebuilder/TestPredicatesTest.java
index edf067d..3d8d5f1 100644
--- a/test-runner/tests/src/android/test/suitebuilder/annotation/HasAnnotationTest.java
+++ b/test-runner/tests/src/android/test/suitebuilder/TestPredicatesTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.test.suitebuilder.annotation;
+package android.test.suitebuilder;
 
-import android.test.suitebuilder.TestMethod;
+import com.android.internal.util.Predicate;
 import junit.framework.TestCase;
 
 import java.lang.annotation.ElementType;
@@ -25,7 +25,7 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
 
-public class HasAnnotationTest extends TestCase {
+public class TestPredicatesTest extends TestCase {
 
     public void testThatMethodWithAnnotationIsReportedAsBeingAnnotated() throws Exception {
         assertTrue(hasExampleAnnotation(ClassWithAnnotation.class, "testWithAnnotation"));
@@ -45,7 +45,7 @@
             throws NoSuchMethodException {
         Method method = aClass.getMethod(methodName);
         TestMethod testMethod = new TestMethod(method, aClass);
-        return new HasAnnotation(Example.class).apply(testMethod);
+        return TestPredicates.hasAnnotation(Example.class).apply(testMethod);
     }
 
     @Retention(RetentionPolicy.RUNTIME)
@@ -73,4 +73,21 @@
         public void testWithoutAnnotation() {
         }
     }
+
+    private static final Predicate<Object> TRUE = new Predicate<Object>() {
+        public boolean apply(Object o) {
+            return true;
+        }
+    };
+
+    private static final Predicate<Object> FALSE = new Predicate<Object>() {
+        public boolean apply(Object o) {
+            return false;
+        }
+    };
+
+    public void testNotPredicate() throws Exception {
+        assertTrue(TestPredicates.not(FALSE).apply(null));
+        assertFalse(TestPredicates.not(TRUE).apply(null));
+    }
 }
diff --git a/test-runner/tests/src/android/test/suitebuilder/annotation/HasClassAnnotationTest.java b/test-runner/tests/src/android/test/suitebuilder/annotation/HasClassAnnotationTest.java
deleted file mode 100644
index 051ea54..0000000
--- a/test-runner/tests/src/android/test/suitebuilder/annotation/HasClassAnnotationTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.suitebuilder.annotation;
-
-import android.test.suitebuilder.TestMethod;
-import junit.framework.TestCase;
-
-import java.lang.reflect.Method;
-
-public class HasClassAnnotationTest extends TestCase {
-
-    public void testShouldTellIfParentClassHasSpecifiedClassification()
-            throws NoSuchMethodException {
-        assertTrue(classHasAnnotation(SmokeTestExample.class, Smoke.class));
-    }
-
-    public void testShouldTellIfParentClassDoesNotHaveSpecifiedClassification()
-            throws NoSuchMethodException {
-        assertFalse(classHasAnnotation(NonSmokeTestExample.class, Smoke.class));
-    }
-
-    private boolean classHasAnnotation(
-            Class<? extends TestCase> aClass,
-            Class<Smoke> expectedClassification) throws NoSuchMethodException {
-        Method method = aClass.getMethod("testSomeTest");
-
-        TestMethod testMethod = new TestMethod(method, aClass);
-        return new HasClassAnnotation(expectedClassification).apply(testMethod);
-    }
-
-    @Smoke
-    static class SmokeTestExample extends TestCase {
-
-        public void testSomeTest() {
-        }
-    }
-
-    static class NonSmokeTestExample extends TestCase {
-
-        public void testSomeTest() {
-        }
-    }
-}
diff --git a/test-runner/tests/src/android/test/suitebuilder/annotation/HasMethodAnnotationTest.java b/test-runner/tests/src/android/test/suitebuilder/annotation/HasMethodAnnotationTest.java
deleted file mode 100644
index c864e28..0000000
--- a/test-runner/tests/src/android/test/suitebuilder/annotation/HasMethodAnnotationTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.suitebuilder.annotation;
-
-import android.test.suitebuilder.TestMethod;
-import junit.framework.TestCase;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-
-
-public class HasMethodAnnotationTest extends TestCase {
-
-    public void testMethodWithSpecifiedAttribute() throws Exception {
-        assertTrue(methodHasAnnotation(AnnotatedMethodExample.class,
-                "testThatIsAnnotated", Smoke.class));
-    }
-
-    public void testMethodWithoutSpecifiedAttribute() throws Exception {
-        assertFalse(methodHasAnnotation(AnnotatedMethodExample.class,
-                "testThatIsNotAnnotated", Smoke.class));
-    }
-
-    private boolean methodHasAnnotation(Class<? extends TestCase> aClass,
-            String methodName,
-            Class<? extends Annotation> expectedClassification
-    ) throws NoSuchMethodException {
-        Method method = aClass.getMethod(methodName);
-        TestMethod testMethod = new TestMethod(method, aClass);
-        return new HasMethodAnnotation(expectedClassification).apply(testMethod);
-    }
-
-    static class AnnotatedMethodExample extends TestCase {
-
-        @Smoke
-        public void testThatIsAnnotated() {
-        }
-
-        public void testThatIsNotAnnotated() {
-        }
-    }
-}
diff --git a/tests/CoreTests/android/core/HeapTest.java b/tests/CoreTests/android/core/HeapTest.java
deleted file mode 100644
index 400d041..0000000
--- a/tests/CoreTests/android/core/HeapTest.java
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.core;
-
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-import android.test.suitebuilder.annotation.Suppress;
-import dalvik.system.VMRuntime;
-import junit.framework.TestCase;
-
-import java.lang.ref.PhantomReference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.SoftReference;
-import java.lang.ref.WeakReference;
-import java.util.LinkedList;
-import java.util.Random;
-
-
-public class HeapTest extends TestCase {
-
-    private static final String TAG = "HeapTest";
-
-    /**
-     * Returns a WeakReference to an object that has no
-     * other references.  This is done in a separate method
-     * to ensure that the Object's address isn't sitting in
-     * a stale local register.
-     */
-    private WeakReference<Object> newRef() {
-        return new WeakReference<Object>(new Object());
-    }
-
-    private static void makeRefs(Object objects[], SoftReference<Object> refs[]) {
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = (Object) new byte[8 * 1024];
-            refs[i] = new SoftReference<Object>(objects[i]);
-        }
-    }
-
-    private static <T> int checkRefs(SoftReference<T> refs[], int last) {
-        int i;
-        int numCleared = 0;
-        for (i = 0; i < refs.length; i++) {
-            Object o = refs[i].get();
-            if (o == null) {
-                numCleared++;
-            }
-        }
-        if (numCleared != last) {
-            Log.i(TAG, "****** " + numCleared + "/" + i + " cleared ******");
-        }
-        return numCleared;
-    }
-
-    private static void clearRefs(Object objects[], int skip) {
-        for (int i = 0; i < objects.length; i += skip) {
-            objects[i] = null;
-        }
-    }
-
-    private static void clearRefs(Object objects[]) {
-        clearRefs(objects, 1);
-    }
-
-    private static <T> void checkRefs(T objects[], SoftReference<T> refs[]) {
-        boolean ok = true;
-
-        for (int i = 0; i < objects.length; i++) {
-            if (refs[i].get() != objects[i]) {
-                ok = false;
-            }
-        }
-        if (!ok) {
-            throw new RuntimeException("Test failed: soft refs not cleared");
-        }
-    }
-
-    @MediumTest
-    public void testGcSoftRefs() throws Exception {
-        final int NUM_REFS = 128;
-
-        Object objects[] = new Object[NUM_REFS];
-        SoftReference<Object> refs[] = new SoftReference[objects.length];
-
-        /* Create a bunch of objects and a parallel array
-         * of SoftReferences.
-         */
-        makeRefs(objects, refs);
-        Runtime.getRuntime().gc();
-
-        /* Let go of some of the hard references to the objects so that
-         * the references can be cleared.
-         */
-        clearRefs(objects, 3);
-
-        /* Collect all softly-reachable objects.
-         */
-        VMRuntime.getRuntime().gcSoftReferences();
-        Runtime.getRuntime().runFinalization();
-
-        /* Make sure that the objects were collected.
-         */
-        checkRefs(objects, refs);
-
-        /* Remove more hard references and re-check.
-         */
-        clearRefs(objects, 2);
-        VMRuntime.getRuntime().gcSoftReferences();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs);
-
-        /* Remove the rest of the references and re-check.
-         */
-        /* Remove more hard references and re-check.
-         */
-        clearRefs(objects);
-        VMRuntime.getRuntime().gcSoftReferences();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs);
-    }
-
-    public void xxtestSoftRefPartialClean() throws Exception {
-        final int NUM_REFS = 128;
-
-        Object objects[] = new Object[NUM_REFS];
-        SoftReference<Object> refs[] = new SoftReference[objects.length];
-
-        /* Create a bunch of objects and a parallel array
-        * of SoftReferences.
-        */
-        makeRefs(objects, refs);
-        Runtime.getRuntime().gc();
-
-        /* Let go of the hard references to the objects so that
-        * the references can be cleared.
-        */
-        clearRefs(objects);
-
-        /* Start creating a bunch of temporary and permanent objects
-        * to drive GC.
-        */
-        final int NUM_OBJECTS = 64 * 1024;
-        Object junk[] = new Object[NUM_OBJECTS];
-        Random random = new Random();
-
-        int i = 0;
-        int mod = 0;
-        int totalSize = 0;
-        int cleared = -1;
-        while (i < junk.length && totalSize < 8 * 1024 * 1024) {
-            int r = random.nextInt(64 * 1024) + 128;
-            Object o = (Object) new byte[r];
-            if (++mod % 16 == 0) {
-                junk[i++] = o;
-                totalSize += r * 4;
-            }
-            cleared = checkRefs(refs, cleared);
-        }
-    }
-
-    private static void makeRefs(Object objects[], WeakReference<Object> refs[]) {
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = new Object();
-            refs[i] = new WeakReference<Object>(objects[i]);
-        }
-    }
-
-    private static <T> void checkRefs(T objects[], WeakReference<T> refs[]) {
-        boolean ok = true;
-
-        for (int i = 0; i < objects.length; i++) {
-            if (refs[i].get() != objects[i]) {
-                ok = false;
-            }
-        }
-        if (!ok) {
-            throw new RuntimeException("Test failed: " +
-                    "weak refs not cleared");
-        }
-    }
-
-    @MediumTest
-    public void testWeakRefs() throws Exception {
-        final int NUM_REFS = 16;
-
-        Object objects[] = new Object[NUM_REFS];
-        WeakReference<Object> refs[] = new WeakReference[objects.length];
-
-        /* Create a bunch of objects and a parallel array
-        * of WeakReferences.
-        */
-        makeRefs(objects, refs);
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs);
-
-        /* Clear out every other strong reference.
-        */
-        for (int i = 0; i < objects.length; i += 2) {
-            objects[i] = null;
-        }
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs);
-
-        /* Clear out the rest of them.
-        */
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = null;
-        }
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs);
-    }
-
-    private static void makeRefs(Object objects[], PhantomReference<Object> refs[],
-            ReferenceQueue<Object> queue) {
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = new Object();
-            refs[i] = new PhantomReference<Object>(objects[i], queue);
-        }
-    }
-
-    static <T> void checkRefs(T objects[], PhantomReference<T> refs[],
-            ReferenceQueue<T> queue) {
-        boolean ok = true;
-
-        /* Make sure that the reference that should be on
-        * the queue are marked as enqueued.  Once we
-        * pull them off the queue, they will no longer
-        * be marked as enqueued.
-        */
-        for (int i = 0; i < objects.length; i++) {
-            if (objects[i] == null && refs[i] != null) {
-                if (!refs[i].isEnqueued()) {
-                    ok = false;
-                }
-            }
-        }
-        if (!ok) {
-            throw new RuntimeException("Test failed: " +
-                    "phantom refs not marked as enqueued");
-        }
-
-        /* Make sure that all of the references on the queue
-        * are supposed to be there.
-        */
-        PhantomReference<T> ref;
-        while ((ref = (PhantomReference<T>) queue.poll()) != null) {
-            /* Find the list index that corresponds to this reference.
-            */
-            int i;
-            for (i = 0; i < objects.length; i++) {
-                if (refs[i] == ref) {
-                    break;
-                }
-            }
-            if (i == objects.length) {
-                throw new RuntimeException("Test failed: " +
-                        "unexpected ref on queue");
-            }
-            if (objects[i] != null) {
-                throw new RuntimeException("Test failed: " +
-                        "reference enqueued for strongly-reachable " +
-                        "object");
-            }
-            refs[i] = null;
-
-            /* TODO: clear doesn't do much, since we're losing the
-            * strong ref to the ref object anyway.  move the ref
-            * into another list.
-            */
-            ref.clear();
-        }
-
-        /* We've visited all of the enqueued references.
-        * Make sure that there aren't any other references
-        * that should have been enqueued.
-        *
-        * NOTE: there is a race condition here;  this assumes
-        * that the VM has serviced all outstanding reference
-        * enqueue() calls.
-        */
-        for (int i = 0; i < objects.length; i++) {
-            if (objects[i] == null && refs[i] != null) {
-//                System.out.println("HeapTest/PhantomRefs: refs[" + i +
-//                        "] should be enqueued");
-                ok = false;
-            }
-        }
-        if (!ok) {
-            throw new RuntimeException("Test failed: " +
-                    "phantom refs not enqueued");
-        }
-    }
-
-    @MediumTest
-    public void testPhantomRefs() throws Exception {
-        final int NUM_REFS = 16;
-
-        Object objects[] = new Object[NUM_REFS];
-        PhantomReference<Object> refs[] = new PhantomReference[objects.length];
-        ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
-
-        /* Create a bunch of objects and a parallel array
-        * of PhantomReferences.
-        */
-        makeRefs(objects, refs, queue);
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs, queue);
-
-        /* Clear out every other strong reference.
-        */
-        for (int i = 0; i < objects.length; i += 2) {
-            objects[i] = null;
-        }
-        // System.out.println("HeapTest/PhantomRefs: cleared evens");
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs, queue);
-
-        /* Clear out the rest of them.
-        */
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = null;
-        }
-        // System.out.println("HeapTest/PhantomRefs: cleared all");
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs, queue);
-    }
-
-    private static int sNumFinalized = 0;
-    private static final Object sLock = new Object();
-
-    private static class FinalizableObject {
-        protected void finalize() {
-            // System.out.println("gc from finalize()");
-            Runtime.getRuntime().gc();
-            synchronized (sLock) {
-                sNumFinalized++;
-            }
-        }
-    }
-
-    private static void makeRefs(FinalizableObject objects[],
-            WeakReference<FinalizableObject> refs[]) {
-        for (int i = 0; i < objects.length; i++) {
-            objects[i] = new FinalizableObject();
-            refs[i] = new WeakReference<FinalizableObject>(objects[i]);
-        }
-    }
-
-    @LargeTest
-    public void testWeakRefsAndFinalizers() throws Exception {
-        final int NUM_REFS = 16;
-
-        FinalizableObject objects[] = new FinalizableObject[NUM_REFS];
-        WeakReference<FinalizableObject> refs[] = new WeakReference[objects.length];
-        int numCleared;
-
-        /* Create a bunch of objects and a parallel array
-        * of WeakReferences.
-        */
-        makeRefs(objects, refs);
-        Runtime.getRuntime().gc();
-        checkRefs(objects, refs);
-
-        /* Clear out every other strong reference.
-        */
-        sNumFinalized = 0;
-        numCleared = 0;
-        for (int i = 0; i < objects.length; i += 2) {
-            objects[i] = null;
-            numCleared++;
-        }
-        // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared evens");
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs);
-        if (sNumFinalized != numCleared) {
-            throw new RuntimeException("Test failed: " +
-                    "expected " + numCleared + " finalizations, saw " +
-                    sNumFinalized);
-        }
-
-        /* Clear out the rest of them.
-        */
-        sNumFinalized = 0;
-        numCleared = 0;
-        for (int i = 0; i < objects.length; i++) {
-            if (objects[i] != null) {
-                objects[i] = null;
-                numCleared++;
-            }
-        }
-        // System.out.println("HeapTest/WeakRefsAndFinalizers: cleared all");
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-        checkRefs(objects, refs);
-        if (sNumFinalized != numCleared) {
-            throw new RuntimeException("Test failed: " +
-                    "expected " + numCleared + " finalizations, saw " +
-                    sNumFinalized);
-        }
-    }
-
-    // TODO: flaky test
-    //@MediumTest
-    public void testOomeLarge() throws Exception {
-        /* Just shy of the typical max heap size so that it will actually
-         * try to allocate it instead of short-circuiting.
-         */
-        final int SIXTEEN_MB = (16 * 1024 * 1024 - 32);
-
-        Boolean sawEx = false;
-        byte a[];
-
-        try {
-            a = new byte[SIXTEEN_MB];
-        } catch (OutOfMemoryError oom) {
-            //Log.i(TAG, "HeapTest/OomeLarge caught " + oom);
-            sawEx = true;
-        }
-
-        if (!sawEx) {
-            throw new RuntimeException("Test failed: " +
-                    "OutOfMemoryError not thrown");
-        }
-    }
-
-    //See bug 1308253 for reasons.
-    @Suppress
-    public void disableTestOomeSmall() throws Exception {
-        final int SIXTEEN_MB = (16 * 1024 * 1024);
-        final int LINK_SIZE = 6 * 4; // estimated size of a LinkedList's node
-
-        Boolean sawEx = false;
-
-        LinkedList<Object> list = new LinkedList<Object>();
-
-        /* Allocate progressively smaller objects to fill up the entire heap.
-         */
-        int objSize = 1 * 1024 * 1024;
-        while (objSize >= LINK_SIZE) {
-            try {
-                for (int i = 0; i < SIXTEEN_MB / objSize; i++) {
-                    list.add((Object)new byte[objSize]);
-                }
-            } catch (OutOfMemoryError oom) {
-                sawEx = true;
-            }
-
-            if (!sawEx) {
-                throw new RuntimeException("Test failed: " +
-                        "OutOfMemoryError not thrown while filling heap");
-            }
-            sawEx = false;
-
-            objSize = (objSize * 4) / 5;
-        }
-    }
-}
diff --git a/tests/net/OWNERS b/tests/net/OWNERS
new file mode 100644
index 0000000..fa26997
--- /dev/null
+++ b/tests/net/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+per-file Android.mk = build.master@android.com
+
+ek@google.com
+hugobenichi@google.com
+lorenzo@google.com
diff --git a/tests/net/java/android/net/IpSecManagerTest.java b/tests/net/java/android/net/IpSecManagerTest.java
new file mode 100644
index 0000000..9f31d27
--- /dev/null
+++ b/tests/net/java/android/net/IpSecManagerTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.system.Os;
+import android.test.AndroidTestCase;
+import com.android.server.IpSecService;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link IpSecManager}. */
+@SmallTest
+@RunWith(JUnit4.class)
+public class IpSecManagerTest {
+
+    private static final int TEST_UDP_ENCAP_PORT = 34567;
+    private static final int DROID_SPI = 0xD1201D;
+
+    private static final InetAddress GOOGLE_DNS_4;
+
+    static {
+        try {
+            // Google Public DNS Addresses;
+            GOOGLE_DNS_4 = InetAddress.getByName("8.8.8.8");
+        } catch (UnknownHostException e) {
+            throw new RuntimeException("Could not resolve DNS Addresses", e);
+        }
+    }
+
+    private IpSecService mMockIpSecService;
+    private IpSecManager mIpSecManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockIpSecService = mock(IpSecService.class);
+        mIpSecManager = new IpSecManager(mMockIpSecService);
+    }
+
+    /*
+     * Allocate a specific SPI
+     * Close SPIs
+     */
+    @Test
+    public void testAllocSpi() throws Exception {
+        int resourceId = 1;
+        IpSecSpiResponse spiResp =
+                new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI);
+        when(mMockIpSecService.reserveSecurityParameterIndex(
+                        eq(IpSecTransform.DIRECTION_IN),
+                        eq(GOOGLE_DNS_4.getHostAddress()),
+                        eq(DROID_SPI),
+                        anyObject()))
+                .thenReturn(spiResp);
+
+        IpSecManager.SecurityParameterIndex droidSpi =
+                mIpSecManager.reserveSecurityParameterIndex(
+                        IpSecTransform.DIRECTION_IN, GOOGLE_DNS_4, DROID_SPI);
+        assertEquals(DROID_SPI, droidSpi.getSpi());
+
+        droidSpi.close();
+
+        verify(mMockIpSecService).releaseSecurityParameterIndex(resourceId);
+    }
+
+    @Test
+    public void testAllocRandomSpi() throws Exception {
+        int resourceId = 1;
+        IpSecSpiResponse spiResp =
+                new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI);
+        when(mMockIpSecService.reserveSecurityParameterIndex(
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        eq(GOOGLE_DNS_4.getHostAddress()),
+                        eq(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX),
+                        anyObject()))
+                .thenReturn(spiResp);
+
+        IpSecManager.SecurityParameterIndex randomSpi =
+                mIpSecManager.reserveSecurityParameterIndex(
+                        IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4);
+
+        assertEquals(DROID_SPI, randomSpi.getSpi());
+
+        randomSpi.close();
+
+        verify(mMockIpSecService).releaseSecurityParameterIndex(resourceId);
+    }
+
+    /*
+     * Throws resource unavailable exception
+     */
+    @Test
+    public void testAllocSpiResUnavaiableExeption() throws Exception {
+        IpSecSpiResponse spiResp =
+                new IpSecSpiResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE, 0, 0);
+        when(mMockIpSecService.reserveSecurityParameterIndex(
+                        anyInt(), anyString(), anyInt(), anyObject()))
+                .thenReturn(spiResp);
+
+        try {
+            mIpSecManager.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4);
+            fail("ResourceUnavailableException was not thrown");
+        } catch (IpSecManager.ResourceUnavailableException e) {
+        }
+    }
+
+    /*
+     * Throws spi unavailable exception
+     */
+    @Test
+    public void testAllocSpiSpiUnavaiableExeption() throws Exception {
+        IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.SPI_UNAVAILABLE, 0, 0);
+        when(mMockIpSecService.reserveSecurityParameterIndex(
+                        anyInt(), anyString(), anyInt(), anyObject()))
+                .thenReturn(spiResp);
+
+        try {
+            mIpSecManager.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4);
+            fail("ResourceUnavailableException was not thrown");
+        } catch (IpSecManager.ResourceUnavailableException e) {
+        }
+    }
+
+    /*
+     * Should throw exception when request spi 0 in IpSecManager
+     */
+    @Test
+    public void testRequestAllocInvalidSpi() throws Exception {
+        try {
+            mIpSecManager.reserveSecurityParameterIndex(
+                    IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4, 0);
+            fail("Able to allocate invalid spi");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
+    public void testOpenEncapsulationSocket() throws Exception {
+        int resourceId = 1;
+        IpSecUdpEncapResponse udpEncapResp =
+                new IpSecUdpEncapResponse(
+                        IpSecManager.Status.OK,
+                        resourceId,
+                        TEST_UDP_ENCAP_PORT,
+                        Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
+        when(mMockIpSecService.openUdpEncapsulationSocket(eq(TEST_UDP_ENCAP_PORT), anyObject()))
+                .thenReturn(udpEncapResp);
+
+        IpSecManager.UdpEncapsulationSocket encapSocket =
+                mIpSecManager.openUdpEncapsulationSocket(TEST_UDP_ENCAP_PORT);
+        assertNotNull(encapSocket.getSocket());
+        assertEquals(TEST_UDP_ENCAP_PORT, encapSocket.getPort());
+
+        encapSocket.close();
+
+        verify(mMockIpSecService).closeUdpEncapsulationSocket(resourceId);
+    }
+
+    @Test
+    public void testOpenEncapsulationSocketOnRandomPort() throws Exception {
+        int resourceId = 1;
+        IpSecUdpEncapResponse udpEncapResp =
+                new IpSecUdpEncapResponse(
+                        IpSecManager.Status.OK,
+                        resourceId,
+                        TEST_UDP_ENCAP_PORT,
+                        Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
+
+        when(mMockIpSecService.openUdpEncapsulationSocket(eq(0), anyObject()))
+                .thenReturn(udpEncapResp);
+
+        IpSecManager.UdpEncapsulationSocket encapSocket =
+                mIpSecManager.openUdpEncapsulationSocket();
+
+        assertNotNull(encapSocket.getSocket());
+        assertEquals(TEST_UDP_ENCAP_PORT, encapSocket.getPort());
+
+        encapSocket.close();
+
+        verify(mMockIpSecService).closeUdpEncapsulationSocket(resourceId);
+    }
+
+    @Test
+    public void testOpenEncapsulationSocketWithInvalidPort() throws Exception {
+        try {
+            mIpSecManager.openUdpEncapsulationSocket(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
+            fail("IllegalArgumentException was not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    // TODO: add test when applicable transform builder interface is available
+}
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 6bf3b6b..bfbb8cc 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -92,6 +92,9 @@
     private final static boolean DROP_MULTICAST = true;
     private final static boolean ALLOW_MULTICAST = false;
 
+    private final static boolean DROP_802_3_FRAMES = true;
+    private final static boolean ALLOW_802_3_FRAMES = false;
+
     private static String label(int code) {
         switch (code) {
             case PASS: return "PASS";
@@ -611,9 +614,9 @@
         private final long mFixedTimeMs = SystemClock.elapsedRealtime();
 
         public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter,
-                IpConnectivityLog log) throws Exception {
+                boolean ieee802_3Filter, IpConnectivityLog log) throws Exception {
             super(new ApfCapabilities(2, 1700, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
-                    ipManagerCallback, multicastFilter, log);
+                    ipManagerCallback, multicastFilter, ieee802_3Filter, log);
         }
 
         // Pretend an RA packet has been received and show it to ApfFilter.
@@ -742,7 +745,8 @@
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
 
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
+        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST,
+                ALLOW_802_3_FRAMES, mLog);
         apfFilter.setLinkProperties(lp);
 
         byte[] program = ipManagerCallback.getApfProgram();
@@ -793,7 +797,8 @@
     @SmallTest
     public void testApfFilterIPv6() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
+        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
+                ALLOW_802_3_FRAMES, mLog);
         byte[] program = ipManagerCallback.getApfProgram();
 
         // Verify empty IPv6 packet is passed
@@ -834,7 +839,8 @@
         LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(link);
 
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
+        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
+                DROP_802_3_FRAMES, mLog);
         apfFilter.setLinkProperties(lp);
 
         byte[] program = ipManagerCallback.getApfProgram();
@@ -896,7 +902,8 @@
         // Verify it can be initialized to on
         ipManagerCallback.resetApfProgramWait();
         apfFilter.shutdown();
-        apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
+        apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST,
+                DROP_802_3_FRAMES, mLog);
         apfFilter.setLinkProperties(lp);
         program = ipManagerCallback.getApfProgram();
         assertDrop(program, mcastv4packet.array());
@@ -911,6 +918,56 @@
         apfFilter.shutdown();
     }
 
+    @SmallTest
+    public void testApfFilter802_3() throws Exception {
+        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
+        LinkProperties lp = new LinkProperties();
+        lp.addLinkAddress(link);
+
+        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
+                ALLOW_802_3_FRAMES, mLog);
+        apfFilter.setLinkProperties(lp);
+
+        byte[] program = ipManagerCallback.getApfProgram();
+
+        // Verify empty packet of 100 zero bytes is passed
+        // Note that eth-type = 0 makes it an IEEE802.3 frame
+        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+        assertPass(program, packet.array());
+
+        // Verify empty packet with IPv4 is passed
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        assertPass(program, packet.array());
+
+        // Verify empty IPv6 packet is passed
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        assertPass(program, packet.array());
+
+        // Now turn on the filter
+        ipManagerCallback.resetApfProgramWait();
+        apfFilter.shutdown();
+        apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
+                DROP_802_3_FRAMES, mLog);
+        apfFilter.setLinkProperties(lp);
+        program = ipManagerCallback.getApfProgram();
+
+        // Verify that IEEE802.3 frame is dropped
+        // In this case ethtype is used for payload length
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)(100 - 14));
+        assertDrop(program, packet.array());
+
+        // Verify that IPv4 (as example of Ethernet II) frame will pass
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        assertPass(program, packet.array());
+
+        // Verify that IPv6 (as example of Ethernet II) frame will pass
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        assertPass(program, packet.array());
+
+        apfFilter.shutdown();
+    }
+
     private byte[] getProgram(MockIpManagerCallback cb, ApfFilter filter, LinkProperties lp) {
         cb.resetApfProgramWait();
         filter.setLinkProperties(lp);
@@ -935,7 +992,8 @@
     @SmallTest
     public void testApfFilterArp() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
-        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
+        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST,
+                DROP_802_3_FRAMES, mLog);
 
         // Verify initially ARP request filter is off, and GARP filter is on.
         verifyArpFilter(ipManagerCallback.getApfProgram(), PASS);
@@ -1056,7 +1114,8 @@
     @SmallTest
     public void testApfFilterRa() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
-        TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST,
+                DROP_802_3_FRAMES, mLog);
         byte[] program = ipManagerCallback.getApfProgram();
 
         final int ROUTER_LIFETIME = 1000;
@@ -1198,7 +1257,8 @@
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
         MockIpManagerCallback cb = new MockIpManagerCallback();
-        TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST,
+                DROP_802_3_FRAMES, mLog);
         for (int i = 0; i < 1000; i++) {
             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
             r.nextBytes(packet);
@@ -1216,7 +1276,8 @@
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
         MockIpManagerCallback cb = new MockIpManagerCallback();
-        TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST, mLog);
+        TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST,
+                DROP_802_3_FRAMES, mLog);
         for (int i = 0; i < 1000; i++) {
             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
             r.nextBytes(packet);
diff --git a/tests/net/java/android/net/ip/IpManagerTest.java b/tests/net/java/android/net/ip/IpManagerTest.java
index 025b017..dc77e22 100644
--- a/tests/net/java/android/net/ip/IpManagerTest.java
+++ b/tests/net/java/android/net/ip/IpManagerTest.java
@@ -16,11 +16,27 @@
 
 package android.net.ip;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AlarmManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.ip.IpManager.Callback;
+import android.net.ip.IpManager.InitialConfiguration;
+import android.net.ip.IpManager.ProvisioningConfiguration;
 import android.os.INetworkManagementService;
 import android.provider.Settings;
 import android.support.test.filters.SmallTest;
@@ -31,11 +47,17 @@
 import com.android.internal.R;
 
 import org.junit.Before;
-import org.junit.runner.RunWith;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Tests for IpManager.
  */
@@ -44,14 +66,21 @@
 public class IpManagerTest {
     private static final int DEFAULT_AVOIDBADWIFI_CONFIG_VALUE = 1;
 
+    private static final String VALID = "VALID";
+    private static final String INVALID = "INVALID";
+
     @Mock private Context mContext;
     @Mock private INetworkManagementService mNMService;
     @Mock private Resources mResources;
+    @Mock private Callback mCb;
+    @Mock private AlarmManager mAlarm;
     private MockContentResolver mContentResolver;
 
-    @Before public void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
                 .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
@@ -61,6 +90,14 @@
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
     }
 
+    private IpManager makeIpManager(String ifname) throws Exception {
+        final IpManager ipm = new IpManager(mContext, ifname, mCb, mNMService);
+        verify(mNMService, timeout(100).times(1)).disableIpv6(ifname);
+        verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(ifname);
+        reset(mNMService);
+        return ipm;
+    }
+
     @Test
     public void testNullCallbackDoesNotThrow() throws Exception {
         final IpManager ipm = new IpManager(mContext, "lo", null, mNMService);
@@ -68,7 +105,153 @@
 
     @Test
     public void testInvalidInterfaceDoesNotThrow() throws Exception {
-        final IpManager.Callback cb = new IpManager.Callback();
-        final IpManager ipm = new IpManager(mContext, "test_wlan0", cb, mNMService);
+        final IpManager ipm = new IpManager(mContext, "test_wlan0", mCb, mNMService);
+    }
+
+    @Test
+    public void testDefaultProvisioningConfiguration() throws Exception {
+        final String iface = "test_wlan0";
+        final IpManager ipm = makeIpManager(iface);
+
+        ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .withoutIPv4()
+                // TODO: mock IpReachabilityMonitor's dependencies (NetworkInterface, PowerManager)
+                // and enable it in this test
+                .withoutIpReachabilityMonitor()
+                .build();
+
+        ipm.startProvisioning(config);
+        verify(mCb, times(1)).setNeighborDiscoveryOffload(true);
+        verify(mCb, timeout(100).times(1)).setFallbackMulticastFilter(false);
+        verify(mCb, never()).onProvisioningFailure(any());
+
+        ipm.stop();
+        verify(mNMService, timeout(100).times(1)).disableIpv6(iface);
+        verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(iface);
+    }
+
+    @Test
+    public void testInitialConfigurations() throws Exception {
+        InitialConfigurationTestCase[] testcases = {
+            validConf("valid IPv4 configuration",
+                    links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("192.0.2.2")),
+            validConf("another valid IPv4 configuration",
+                    links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns()),
+            validConf("valid IPv6 configurations",
+                    links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
+                    prefixes("2001:db8:dead:beef::/64", "fe80::/64"),
+                    dns("2001:db8:dead:beef:f00::02")),
+            validConf("valid IPv6 configurations",
+                    links("fe80::1/64"), prefixes("fe80::/64"), dns()),
+            validConf("valid IPv6/v4 configuration",
+                    links("2001:db8:dead:beef:f00::a0/48", "192.0.2.12/24"),
+                    prefixes("2001:db8:dead:beef::/64", "192.0.2.0/24"),
+                    dns("192.0.2.2", "2001:db8:dead:beef:f00::02")),
+            validConf("valid IPv6 configuration without any GUA.",
+                    links("fd00:1234:5678::1/48"),
+                    prefixes("fd00:1234:5678::/48"),
+                    dns("fd00:1234:5678::1000")),
+
+            invalidConf("v4 addr and dns not in any prefix",
+                    links("192.0.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
+            invalidConf("v4 addr not in any prefix",
+                    links("198.51.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
+            invalidConf("v4 dns addr not in any prefix",
+                    links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("198.51.100.2")),
+            invalidConf("v6 addr not in any prefix",
+                    links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
+                    prefixes("2001:db8:dead:beef::/64"),
+                    dns("2001:db8:dead:beef:f00::02")),
+            invalidConf("v6 dns addr not in any prefix",
+                    links("fe80::1/64"), prefixes("fe80::/64"), dns("2001:db8:dead:beef:f00::02")),
+            invalidConf("default ipv6 route and no GUA",
+                    links("fd01:1111:2222:3333::a0/128"), prefixes("::/0"), dns()),
+            invalidConf("invalid v6 prefix length",
+                    links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/32"),
+                    dns()),
+            invalidConf("another invalid v6 prefix length",
+                    links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/72"),
+                    dns())
+        };
+
+        for (InitialConfigurationTestCase testcase : testcases) {
+            if (testcase.config.isValid() != testcase.isValid) {
+                fail(testcase.errorMessage());
+            }
+        }
+    }
+
+    static class InitialConfigurationTestCase {
+        String descr;
+        boolean isValid;
+        InitialConfiguration config;
+        public String errorMessage() {
+            return String.format("%s: expected configuration %s to be %s, but was %s",
+                    descr, config, validString(isValid), validString(!isValid));
+        }
+    }
+
+    static String validString(boolean isValid) {
+        return isValid ? VALID : INVALID;
+    }
+
+    static InitialConfigurationTestCase validConf(String descr, Set<LinkAddress> links,
+            Set<IpPrefix> prefixes, Set<InetAddress> dns) {
+        return confTestCase(descr, true, conf(links, prefixes, dns));
+    }
+
+    static InitialConfigurationTestCase invalidConf(String descr, Set<LinkAddress> links,
+            Set<IpPrefix> prefixes, Set<InetAddress> dns) {
+        return confTestCase(descr, false, conf(links, prefixes, dns));
+    }
+
+    static InitialConfigurationTestCase confTestCase(
+            String descr, boolean isValid, InitialConfiguration config) {
+        InitialConfigurationTestCase testcase = new InitialConfigurationTestCase();
+        testcase.descr = descr;
+        testcase.isValid = isValid;
+        testcase.config = config;
+        return testcase;
+    }
+
+    static InitialConfiguration conf(
+            Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns) {
+        InitialConfiguration conf = new InitialConfiguration();
+        conf.ipAddresses.addAll(links);
+        conf.directlyConnectedRoutes.addAll(prefixes);
+        conf.dnsServers.addAll(dns);
+        return conf;
+    }
+
+    static Set<IpPrefix> prefixes(String... prefixes) {
+        return mapIntoSet(prefixes, IpPrefix::new);
+    }
+
+    static Set<LinkAddress> links(String... addresses) {
+        return mapIntoSet(addresses, LinkAddress::new);
+    }
+
+    static Set<InetAddress> ips(String... addresses) {
+        return mapIntoSet(addresses, InetAddress::getByName);
+    }
+
+    static Set<InetAddress> dns(String... addresses) {
+        return ips(addresses);
+    }
+
+    static <A, B> Set<B> mapIntoSet(A[] in, Fn<A, B> fn) {
+        Set<B> out = new HashSet<>(in.length);
+        for (A item : in) {
+            try {
+                out.add(fn.call(item));
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return out;
+    }
+
+    interface Fn<A,B> {
+        B call(A a) throws Exception;
     }
 }
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
new file mode 100644
index 0000000..f849689
--- /dev/null
+++ b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.ip;
+
+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 android.net.util.SharedLog;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * Tests for IpReachabilityMonitor.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpReachabilityMonitorTest {
+
+    @Mock IpReachabilityMonitor.Callback mCallback;
+    @Mock IpReachabilityMonitor.Dependencies mDependencies;
+    @Mock SharedLog mLog;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    IpReachabilityMonitor makeMonitor() {
+        return new IpReachabilityMonitor("fake0", 1, mLog, mCallback, null, mDependencies);
+    }
+
+    @Test
+    public void testNothing() {
+        IpReachabilityMonitor monitor = makeMonitor();
+    }
+}
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
index adf6998..f77608f 100644
--- a/tests/net/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -349,7 +349,6 @@
                 chan.connect(mContext, this, msg.replyTo);
                 chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
             }
-
         }
 
         public static MockServiceHandler create(Context context) {
diff --git a/tests/net/java/android/net/util/SharedLogTest.java b/tests/net/java/android/net/util/SharedLogTest.java
index 3957cb0..d46facf 100644
--- a/tests/net/java/android/net/util/SharedLogTest.java
+++ b/tests/net/java/android/net/util/SharedLogTest.java
@@ -33,8 +33,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SharedLogTest {
-    private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}\\.\\d{3}";
-    private static final String TIMESTAMP = "HH:MM:SS.xxx";
+    private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}";
+    private static final String TIMESTAMP = "HH:MM:SS";
 
     @Test
     public void testBasicOperation() {
@@ -85,7 +85,7 @@
             String got = lines[i];
             String want = expected[i];
             assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want));
-            assertTrue(String.format("'%s' did not contain a HH:MM:SS.xxx timestamp", got),
+            assertTrue(String.format("'%s' did not contain a %s timestamp", got, TIMESTAMP),
                     got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP));
         }
     }
diff --git a/tests/net/java/com/android/internal/util/TestUtils.java b/tests/net/java/com/android/internal/util/TestUtils.java
new file mode 100644
index 0000000..c9fa340
--- /dev/null
+++ b/tests/net/java/com/android/internal/util/TestUtils.java
@@ -0,0 +1,54 @@
+/*
+ * 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.internal.util;
+
+import static org.junit.Assert.fail;
+
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+
+public final class TestUtils {
+    private TestUtils() { }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Block until the given Looper becomes idle, or until timeoutMs has passed.
+     */
+    public static void waitForIdleLooper(Looper looper, long timeoutMs) {
+        waitForIdleHandler(new Handler(looper), timeoutMs);
+    }
+
+    /**
+     * Block until the given Handler becomes idle, or until timeoutMs has passed.
+     */
+    public static void waitForIdleHandler(Handler handler, long timeoutMs) {
+        final ConditionVariable cv = new ConditionVariable();
+        handler.post(() -> cv.open());
+        if (!cv.block(timeoutMs)) {
+            fail(handler.toString() + " did not become idle after " + timeoutMs + " ms");
+        }
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 0263c57..11e36bd 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -19,10 +19,13 @@
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.NetworkCapabilities.*;
 
+import static com.android.internal.util.TestUtils.waitForIdleHandler;
+
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
@@ -111,6 +114,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BooleanSupplier;
+import java.util.function.Predicate;
 
 /**
  * Tests for {@link ConnectivityService}.
@@ -211,20 +215,8 @@
         }
     }
 
-    /**
-     * Block until the given handler becomes idle, or until timeoutMs has passed.
-     */
-    private static void waitForIdleHandler(HandlerThread handlerThread, int timeoutMs) {
-        final ConditionVariable cv = new ConditionVariable();
-        final Handler handler = new Handler(handlerThread.getLooper());
-        handler.post(() -> cv.open());
-        if (!cv.block(timeoutMs)) {
-            fail("HandlerThread " + handlerThread.getName() +
-                    " did not become idle after " + timeoutMs + " ms");
-        }
-    }
-
-    public void waitForIdle(int timeoutMs) {
+    public void waitForIdle(int timeoutMsAsInt) {
+        long timeoutMs = timeoutMsAsInt;
         waitForIdleHandler(mService.mHandlerThread, timeoutMs);
         waitForIdle(mCellNetworkAgent, timeoutMs);
         waitForIdle(mWiFiNetworkAgent, timeoutMs);
@@ -232,7 +224,7 @@
         waitForIdleHandler(mService.mHandlerThread, timeoutMs);
     }
 
-    public void waitForIdle(MockNetworkAgent agent, int timeoutMs) {
+    public void waitForIdle(MockNetworkAgent agent, long timeoutMs) {
         if (agent == null) {
             return;
         }
@@ -326,6 +318,9 @@
                 case TRANSPORT_CELLULAR:
                     mScore = 50;
                     break;
+                case TRANSPORT_WIFI_AWARE:
+                    mScore = 20;
+                    break;
                 default:
                     throw new UnsupportedOperationException("unimplemented network type");
             }
@@ -407,6 +402,15 @@
          * @param validated Indicate if network should pretend to be validated.
          */
         public void connect(boolean validated) {
+            connect(validated, true);
+        }
+
+        /**
+         * Transition this NetworkAgent to CONNECTED state.
+         * @param validated Indicate if network should pretend to be validated.
+         * @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
+         */
+        public void connect(boolean validated, boolean hasInternet) {
             assertEquals("MockNetworkAgents can only be connected once",
                     mNetworkInfo.getDetailedState(), DetailedState.IDLE);
             assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET));
@@ -429,7 +433,9 @@
                 };
                 mCm.registerNetworkCallback(request, callback);
             }
-            addCapability(NET_CAPABILITY_INTERNET);
+            if (hasInternet) {
+                addCapability(NET_CAPABILITY_INTERNET);
+            }
 
             connectWithoutInternet();
 
@@ -783,7 +789,10 @@
      * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens.
      */
     static private void waitFor(ConditionVariable conditionVariable) {
-        assertTrue(conditionVariable.block(TIMEOUT_MS));
+        if (conditionVariable.block(TIMEOUT_MS)) {
+            return;
+        }
+        fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms");
     }
 
     @Override
@@ -838,7 +847,7 @@
             case TRANSPORT_CELLULAR:
                 return TYPE_MOBILE;
             default:
-                throw new IllegalStateException("Unknown transport " + transport);
+                return TYPE_NONE;
         }
     }
 
@@ -849,6 +858,9 @@
         // Test getActiveNetwork()
         assertNotNull(mCm.getActiveNetwork());
         assertEquals(mCm.getActiveNetwork(), mCm.getActiveNetworkForUid(Process.myUid()));
+        if (!NetworkCapabilities.isValidTransport(transport)) {
+            throw new IllegalStateException("Unknown transport " + transport);
+        }
         switch (transport) {
             case TRANSPORT_WIFI:
                 assertEquals(mCm.getActiveNetwork(), mWiFiNetworkAgent.getNetwork());
@@ -857,7 +869,7 @@
                 assertEquals(mCm.getActiveNetwork(), mCellNetworkAgent.getNetwork());
                 break;
             default:
-                throw new IllegalStateException("Unknown transport" + transport);
+                break;
         }
         // Test getNetworkInfo(Network)
         assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork()));
@@ -869,13 +881,14 @@
     }
 
     private void verifyNoNetwork() {
+        waitForIdle();
         // Test getActiveNetworkInfo()
         assertNull(mCm.getActiveNetworkInfo());
         // Test getActiveNetwork()
         assertNull(mCm.getActiveNetwork());
         assertNull(mCm.getActiveNetworkForUid(Process.myUid()));
         // Test getAllNetworks()
-        assertEquals(0, mCm.getAllNetworks().length);
+        assertEmpty(mCm.getAllNetworks());
     }
 
     /**
@@ -916,7 +929,7 @@
         mCellNetworkAgent.connect(true);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
-        assertEquals(2, mCm.getAllNetworks().length);
+        assertLength(2, mCm.getAllNetworks());
         assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
                 mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
         assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) ||
@@ -926,7 +939,7 @@
         mWiFiNetworkAgent.connect(true);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
-        assertEquals(2, mCm.getAllNetworks().length);
+        assertLength(2, mCm.getAllNetworks());
         assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
                 mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
         assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) ||
@@ -934,9 +947,9 @@
         // Test cellular linger timeout.
         waitFor(mCellNetworkAgent.getDisconnectedCV());
         waitForIdle();
-        assertEquals(1, mCm.getAllNetworks().length);
+        assertLength(1, mCm.getAllNetworks());
         verifyActiveNetwork(TRANSPORT_WIFI);
-        assertEquals(1, mCm.getAllNetworks().length);
+        assertLength(1, mCm.getAllNetworks());
         assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork());
         // Test WiFi disconnect.
         cv = waitForConnectivityBroadcasts(1);
@@ -1277,7 +1290,26 @@
             return expectCallback(state, agent, TIMEOUT_MS);
         }
 
-        void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) {
+        CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn) {
+            return expectCallbackLike(fn, TIMEOUT_MS);
+        }
+
+        CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn, int timeoutMs) {
+            int timeLeft = timeoutMs;
+            while (timeLeft > 0) {
+                long start = SystemClock.elapsedRealtime();
+                CallbackInfo info = nextCallback(timeLeft);
+                if (fn.test(info)) {
+                    return info;
+                }
+                timeLeft -= (SystemClock.elapsedRealtime() - start);
+            }
+            fail("Did not receive expected callback after " + timeoutMs + "ms");
+            return null;
+        }
+
+        void expectAvailableCallbacks(
+                MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) {
             expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
             if (expectSuspended) {
                 expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
@@ -1834,26 +1866,18 @@
     @SmallTest
     public void testNoMutableNetworkRequests() throws Exception {
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0);
-        NetworkRequest.Builder builder = new NetworkRequest.Builder();
-        builder.addCapability(NET_CAPABILITY_VALIDATED);
-        try {
-            mCm.requestNetwork(builder.build(), new NetworkCallback());
-            fail();
-        } catch (IllegalArgumentException expected) {}
-        try {
-            mCm.requestNetwork(builder.build(), pendingIntent);
-            fail();
-        } catch (IllegalArgumentException expected) {}
-        builder = new NetworkRequest.Builder();
-        builder.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
-        try {
-            mCm.requestNetwork(builder.build(), new NetworkCallback());
-            fail();
-        } catch (IllegalArgumentException expected) {}
-        try {
-            mCm.requestNetwork(builder.build(), pendingIntent);
-            fail();
-        } catch (IllegalArgumentException expected) {}
+        NetworkRequest request1 = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_VALIDATED)
+                .build();
+        NetworkRequest request2 = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
+                .build();
+
+        Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        assertException(() -> { mCm.requestNetwork(request1, new NetworkCallback()); }, expected);
+        assertException(() -> { mCm.requestNetwork(request1, pendingIntent); }, expected);
+        assertException(() -> { mCm.requestNetwork(request2, new NetworkCallback()); }, expected);
+        assertException(() -> { mCm.requestNetwork(request2, pendingIntent); }, expected);
     }
 
     @SmallTest
@@ -1865,7 +1889,7 @@
         mCellNetworkAgent.connectWithoutInternet();
         waitFor(cv);
         waitForIdle();
-        assertEquals(0, mCm.getAllNetworks().length);
+        assertEmpty(mCm.getAllNetworks());
         verifyNoNetwork();
 
         // Test bringing up validated WiFi.
@@ -2539,7 +2563,7 @@
         assertTrue(testFactory.getMyStartRequested());
 
         // Bring up cell data and check that the factory stops looking.
-        assertEquals(1, mCm.getAllNetworks().length);
+        assertLength(1, mCm.getAllNetworks());
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         testFactory.expectAddRequests(2);  // Because the cell request changes score twice.
         mCellNetworkAgent.connect(true);
@@ -2550,7 +2574,7 @@
         // Check that cell data stays up.
         waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
-        assertEquals(2, mCm.getAllNetworks().length);
+        assertLength(2, mCm.getAllNetworks());
 
         // Turn off mobile data always on and expect the request to disappear...
         testFactory.expectRemoveRequests(1);
@@ -2559,7 +2583,7 @@
 
         // ...  and cell data to be torn down.
         cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        assertEquals(1, mCm.getAllNetworks().length);
+        assertLength(1, mCm.getAllNetworks());
 
         testFactory.unregister();
         mCm.unregisterNetworkCallback(cellNetworkCallback);
@@ -2780,19 +2804,17 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        final int requestTimeoutMs = 100;
+        final int requestTimeoutMs = 50;
         mCm.requestNetwork(nr, networkCallback, requestTimeoutMs);
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        final int assertTimeoutMs = 150;
+        final int assertTimeoutMs = 100;
         networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs);
-        sleepFor(20);
         mWiFiNetworkAgent.disconnect();
         networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
-        // pass timeout and validate that UNAVAILABLE is not called
-        sleepFor(100);
+        // Validate that UNAVAILABLE is not called
         networkCallback.assertNoCallback();
     }
 
@@ -2819,24 +2841,20 @@
     }
 
     /**
-     * Validate that when a network request is unregistered (cancelled) the time-out for that
-     * request doesn't trigger the onUnavailable() callback.
+     * Validate that when a network request is unregistered (cancelled), no posterior event can
+     * trigger the callback.
      */
     @SmallTest
-    public void testTimedoutAfterUnregisteredNetworkRequest() {
+    public void testNoCallbackAfterUnregisteredNetworkRequest() {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         final int timeoutMs = 10;
+
         mCm.requestNetwork(nr, networkCallback, timeoutMs);
-
-        // remove request
         mCm.unregisterNetworkCallback(networkCallback);
-
-        // pass timeout and validate that no callbacks
-        // Note: doesn't validate that nothing called from CS since even if called the CM already
-        // unregisters the callback and won't pass it through!
-        sleepFor(15);
+        // Regardless of the timeout, unregistering the callback in ConnectivityManager ensures
+        // that this callback will not be called.
         networkCallback.assertNoCallback();
 
         // create a network satisfying request - validate that request not triggered
@@ -3255,12 +3273,87 @@
         }
     }
 
-    /* test utilities */
-    // TODO: eliminate all usages of sleepFor and replace by proper timeouts/waitForIdle.
-    static private void sleepFor(int ms) {
+    @SmallTest
+    public void testNetworkInfoOfTypeNone() {
+        ConditionVariable broadcastCV = waitForConnectivityBroadcasts(1);
+
+        verifyNoNetwork();
+        MockNetworkAgent wifiAware = new MockNetworkAgent(TRANSPORT_WIFI_AWARE);
+        assertNull(mCm.getActiveNetworkInfo());
+
+        Network[] allNetworks = mCm.getAllNetworks();
+        assertLength(1, allNetworks);
+        Network network = allNetworks[0];
+        NetworkCapabilities capabilities = mCm.getNetworkCapabilities(network);
+        assertTrue(capabilities.hasTransport(TRANSPORT_WIFI_AWARE));
+
+        final NetworkRequest request =
+                new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI_AWARE).build();
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(request, callback);
+
+        // Bring up wifi aware network.
+        wifiAware.connect(false, false);
+        callback.expectAvailableCallbacks(wifiAware);
+
+        assertNull(mCm.getActiveNetworkInfo());
+        assertNull(mCm.getActiveNetwork());
+        // TODO: getAllNetworkInfo is dirty and returns a non-empty array right from the start
+        // of this test. Fix it and uncomment the assert below.
+        //assertEmpty(mCm.getAllNetworkInfo());
+
+        // Disconnect wifi aware network.
+        wifiAware.disconnect();
+        callback.expectCallbackLike((info) -> info.state == CallbackState.LOST, TIMEOUT_MS);
+        mCm.unregisterNetworkCallback(callback);
+
+        verifyNoNetwork();
+        if (broadcastCV.block(10)) {
+            fail("expected no broadcast, but got CONNECTIVITY_ACTION broadcast");
+        }
+    }
+
+    @SmallTest
+    public void testDeprecatedAndUnsupportedOperations() throws Exception {
+        final int TYPE_NONE = ConnectivityManager.TYPE_NONE;
+        assertNull(mCm.getNetworkInfo(TYPE_NONE));
+        assertNull(mCm.getNetworkForType(TYPE_NONE));
+        assertNull(mCm.getLinkProperties(TYPE_NONE));
+        assertFalse(mCm.isNetworkSupported(TYPE_NONE));
+
+        assertException(() -> { mCm.networkCapabilitiesForType(TYPE_NONE); },
+                IllegalArgumentException.class);
+
+        Class<UnsupportedOperationException> unsupported = UnsupportedOperationException.class;
+        assertException(() -> { mCm.startUsingNetworkFeature(TYPE_WIFI, ""); }, unsupported);
+        assertException(() -> { mCm.stopUsingNetworkFeature(TYPE_WIFI, ""); }, unsupported);
+        // TODO: let test context have configuration application target sdk version
+        // and test that pre-M requesting for TYPE_NONE sends back APN_REQUEST_FAILED
+        assertException(() -> { mCm.startUsingNetworkFeature(TYPE_NONE, ""); }, unsupported);
+        assertException(() -> { mCm.stopUsingNetworkFeature(TYPE_NONE, ""); }, unsupported);
+        assertException(() -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); }, unsupported);
+    }
+
+    private static <T> void assertEmpty(T[] ts) {
+        int length = ts.length;
+        assertEquals("expected empty array, but length was " + length, 0, length);
+    }
+
+    private static <T> void assertLength(int expected, T[] got) {
+        int length = got.length;
+        assertEquals(String.format("expected array of length %s, but length was %s for %s",
+                expected, length, Arrays.toString(got)), expected, length);
+    }
+
+    private static <T> void assertException(Runnable block, Class<T> expected) {
         try {
-            Thread.sleep(ms);
-        } catch (InterruptedException e) {
+            block.run();
+            fail("Expected exception of type " + expected);
+        } catch (Exception got) {
+            if (!got.getClass().equals(expected)) {
+                fail("Expected exception of type " + expected + " but got " + got);
+            }
+            return;
         }
     }
 }
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
new file mode 100644
index 0000000..23fee28
--- /dev/null
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -0,0 +1,435 @@
+/*
+ * 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;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.EADDRINUSE;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.IpSecAlgorithm;
+import android.net.IpSecConfig;
+import android.net.IpSecManager;
+import android.net.IpSecSpiResponse;
+import android.net.IpSecTransform;
+import android.net.IpSecTransformResponse;
+import android.net.IpSecUdpEncapResponse;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.support.test.filters.SmallTest;
+import android.system.ErrnoException;
+import android.system.Os;
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link IpSecService}. */
+@SmallTest
+@RunWith(JUnit4.class)
+public class IpSecServiceTest {
+
+    private static final int DROID_SPI = 0xD1201D;
+    private static final int DROID_SPI2 = DROID_SPI + 1;
+    private static final int TEST_UDP_ENCAP_INVALID_PORT = 100;
+    private static final int TEST_UDP_ENCAP_PORT_OUT_RANGE = 100000;
+    private static final int TEST_UDP_ENCAP_PORT = 34567;
+
+    private static final String IPV4_LOOPBACK = "127.0.0.1";
+    private static final String IPV4_ADDR = "192.168.0.2";
+
+    private static final InetAddress INADDR_ANY;
+
+    static {
+        try {
+            INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static final int[] DIRECTIONS =
+            new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
+    private static final byte[] CRYPT_KEY = {
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+        0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+        0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
+    };
+    private static final byte[] AUTH_KEY = {
+        0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
+        0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
+    };
+
+    Context mMockContext;
+    INetd mMockNetd;
+    IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
+    IpSecService mIpSecService;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockContext = mock(Context.class);
+        mMockNetd = mock(INetd.class);
+        mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
+        mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
+
+        // Injecting mock netd
+        when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
+    }
+
+    @Test
+    public void testIpSecServiceCreate() throws InterruptedException {
+        IpSecService ipSecSrv = IpSecService.create(mMockContext);
+        assertNotNull(ipSecSrv);
+    }
+
+    @Test
+    public void testIpSecServiceReserveSpi() throws Exception {
+        when(mMockNetd.ipSecAllocateSpi(
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        eq(IPV4_LOOPBACK),
+                        eq(DROID_SPI)))
+                .thenReturn(DROID_SPI);
+
+        IpSecSpiResponse spiResp =
+                mIpSecService.reserveSecurityParameterIndex(
+                        IpSecTransform.DIRECTION_OUT, IPV4_LOOPBACK, DROID_SPI, new Binder());
+        assertEquals(IpSecManager.Status.OK, spiResp.status);
+        assertEquals(DROID_SPI, spiResp.spi);
+    }
+
+    @Test
+    public void testReleaseSecurityParameterIndex() throws Exception {
+        when(mMockNetd.ipSecAllocateSpi(
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        eq(IPV4_LOOPBACK),
+                        eq(DROID_SPI)))
+                .thenReturn(DROID_SPI);
+
+        IpSecSpiResponse spiResp =
+                mIpSecService.reserveSecurityParameterIndex(
+                        IpSecTransform.DIRECTION_OUT, IPV4_LOOPBACK, DROID_SPI, new Binder());
+
+        mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
+
+        verify(mMockNetd)
+                .ipSecDeleteSecurityAssociation(
+                        eq(spiResp.resourceId), anyInt(), anyString(), anyString(), eq(DROID_SPI));
+    }
+
+    @Test
+    public void testReleaseInvalidSecurityParameterIndex() throws Exception {
+        try {
+            mIpSecService.releaseSecurityParameterIndex(1);
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    /** This function finds an available port */
+    int findUnusedPort() throws Exception {
+        // Get an available port.
+        ServerSocket s = new ServerSocket(0);
+        int port = s.getLocalPort();
+        s.close();
+        return port;
+    }
+
+    @Test
+    public void testOpenAndCloseUdpEncapsulationSocket() throws Exception {
+        int localport = findUnusedPort();
+
+        IpSecUdpEncapResponse udpEncapResp =
+                mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+        assertNotNull(udpEncapResp);
+        assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+        assertEquals(localport, udpEncapResp.port);
+
+        mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+        udpEncapResp.fileDescriptor.close();
+
+        // TODO: Added check for the resource tracker
+    }
+
+    @Test
+    public void testOpenUdpEncapsulationSocketAfterClose() throws Exception {
+        int localport = findUnusedPort();
+        IpSecUdpEncapResponse udpEncapResp =
+                mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+        assertNotNull(udpEncapResp);
+        assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+        assertEquals(localport, udpEncapResp.port);
+
+        mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+        udpEncapResp.fileDescriptor.close();
+
+        /** Check if localport is available. */
+        FileDescriptor newSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+        Os.bind(newSocket, INADDR_ANY, localport);
+        Os.close(newSocket);
+    }
+
+    /**
+     * This function checks if the IpSecService holds the reserved port. If
+     * closeUdpEncapsulationSocket is not called, the socket cleanup should not be complete.
+     */
+    @Test
+    public void testUdpEncapPortNotReleased() throws Exception {
+        int localport = findUnusedPort();
+        IpSecUdpEncapResponse udpEncapResp =
+                mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+        assertNotNull(udpEncapResp);
+        assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+        assertEquals(localport, udpEncapResp.port);
+
+        udpEncapResp.fileDescriptor.close();
+
+        FileDescriptor newSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+        try {
+            Os.bind(newSocket, INADDR_ANY, localport);
+            fail("ErrnoException not thrown");
+        } catch (ErrnoException e) {
+            assertEquals(EADDRINUSE, e.errno);
+        }
+        mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+    }
+
+    @Test
+    public void testOpenUdpEncapsulationSocketOnRandomPort() throws Exception {
+        IpSecUdpEncapResponse udpEncapResp =
+                mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+        assertNotNull(udpEncapResp);
+        assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+        mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+        udpEncapResp.fileDescriptor.close();
+    }
+
+    @Test
+    public void testOpenUdpEncapsulationSocketPortRange() throws Exception {
+        try {
+            mIpSecService.openUdpEncapsulationSocket(TEST_UDP_ENCAP_INVALID_PORT, new Binder());
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            mIpSecService.openUdpEncapsulationSocket(TEST_UDP_ENCAP_PORT_OUT_RANGE, new Binder());
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
+    public void testOpenUdpEncapsulationSocketTwice() throws Exception {
+        int localport = findUnusedPort();
+
+        IpSecUdpEncapResponse udpEncapResp =
+                mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+        assertNotNull(udpEncapResp);
+        assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+        assertEquals(localport, udpEncapResp.port);
+        mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+
+        IpSecUdpEncapResponse testUdpEncapResp =
+                mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+        assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, testUdpEncapResp.status);
+
+        mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+        udpEncapResp.fileDescriptor.close();
+    }
+
+    @Test
+    public void testCloseInvalidUdpEncapsulationSocket() throws Exception {
+        try {
+            mIpSecService.closeUdpEncapsulationSocket(1);
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    IpSecConfig buildIpSecConfig() throws Exception {
+        IpSecManager ipSecManager = new IpSecManager(mIpSecService);
+
+        // Mocking the netd to allocate SPI
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyInt(), anyString(), anyString(), anyInt()))
+                .thenReturn(DROID_SPI)
+                .thenReturn(DROID_SPI2);
+
+        IpSecAlgorithm encryptAlgo = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
+        IpSecAlgorithm authAlgo =
+                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 8);
+
+        InetAddress localAddr = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
+
+        /** Allocate and add SPI records in the IpSecService through IpSecManager interface. */
+        IpSecManager.SecurityParameterIndex outSpi =
+                ipSecManager.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, localAddr);
+        IpSecManager.SecurityParameterIndex inSpi =
+                ipSecManager.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_IN, localAddr);
+
+        IpSecConfig ipSecConfig =
+                new IpSecTransform.Builder(mMockContext)
+                        .setSpi(IpSecTransform.DIRECTION_OUT, outSpi)
+                        .setSpi(IpSecTransform.DIRECTION_IN, inSpi)
+                        .setEncryption(IpSecTransform.DIRECTION_OUT, encryptAlgo)
+                        .setAuthentication(IpSecTransform.DIRECTION_OUT, authAlgo)
+                        .setEncryption(IpSecTransform.DIRECTION_IN, encryptAlgo)
+                        .setAuthentication(IpSecTransform.DIRECTION_IN, authAlgo)
+                        .getIpSecConfig();
+        return ipSecConfig;
+    }
+
+    @Test
+    public void testCreateTransportModeTransform() throws Exception {
+        IpSecConfig ipSecConfig = buildIpSecConfig();
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+        assertEquals(IpSecManager.Status.OK, createTransformResp.status);
+
+        verify(mMockNetd)
+                .ipSecAddSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        anyString(),
+                        anyLong(),
+                        eq(DROID_SPI),
+                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
+                        eq(AUTH_KEY),
+                        anyInt(),
+                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
+                        eq(CRYPT_KEY),
+                        anyInt(),
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+        verify(mMockNetd)
+                .ipSecAddSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_IN),
+                        anyString(),
+                        anyString(),
+                        anyLong(),
+                        eq(DROID_SPI2),
+                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
+                        eq(AUTH_KEY),
+                        anyInt(),
+                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
+                        eq(CRYPT_KEY),
+                        anyInt(),
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+    }
+
+    @Test
+    public void testDeleteTransportModeTransform() throws Exception {
+        IpSecConfig ipSecConfig = buildIpSecConfig();
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+        mIpSecService.deleteTransportModeTransform(createTransformResp.resourceId);
+
+        verify(mMockNetd)
+                .ipSecDeleteSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        anyString(),
+                        eq(DROID_SPI));
+        verify(mMockNetd)
+                .ipSecDeleteSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        eq(IpSecTransform.DIRECTION_IN),
+                        anyString(),
+                        anyString(),
+                        eq(DROID_SPI2));
+    }
+
+    @Test
+    public void testDeleteInvalidTransportModeTransform() throws Exception {
+        try {
+            mIpSecService.deleteTransportModeTransform(1);
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
+    public void testApplyTransportModeTransform() throws Exception {
+        IpSecConfig ipSecConfig = buildIpSecConfig();
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
+
+        int resourceId = createTransformResp.resourceId;
+        mIpSecService.applyTransportModeTransform(pfd, resourceId);
+
+        verify(mMockNetd)
+                .ipSecApplyTransportModeTransform(
+                        eq(pfd.getFileDescriptor()),
+                        eq(resourceId),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        anyString(),
+                        eq(DROID_SPI));
+        verify(mMockNetd)
+                .ipSecApplyTransportModeTransform(
+                        eq(pfd.getFileDescriptor()),
+                        eq(resourceId),
+                        eq(IpSecTransform.DIRECTION_IN),
+                        anyString(),
+                        anyString(),
+                        eq(DROID_SPI2));
+    }
+
+    @Test
+    public void testRemoveTransportModeTransform() throws Exception {
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
+        mIpSecService.removeTransportModeTransform(pfd, 1);
+
+        verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index f201bc7..911347c 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -16,6 +16,16 @@
 
 package com.android.server.connectivity;
 
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
@@ -37,15 +47,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 public class NetworkNotificationManagerTest extends TestCase {
 
     static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
@@ -140,4 +141,47 @@
 
         verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
     }
+
+    @SmallTest
+    public void testDuplicatedNotificationsNoInternetThenSignIn() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        // Show first NO_INTERNET
+        mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(NO_INTERNET.eventId), any(), any());
+
+        // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET
+        mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .cancelAsUser(eq(tag), eq(NO_INTERNET.eventId), any());
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(SIGN_IN.eventId), any(), any());
+
+        // Network disconnects
+        mManager.clearNotification(id);
+        verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
+    }
+
+    @SmallTest
+    public void testDuplicatedNotificationsSignInThenNoInternet() {
+        final int id = 101;
+        final String tag = NetworkNotificationManager.tagFor(id);
+
+        // Show first SIGN_IN
+        mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(tag), eq(SIGN_IN.eventId), any(), any());
+        reset(mNotificationManager);
+
+        // NO_INTERNET arrives after, but is ignored.
+        mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, never()).cancelAsUser(any(), anyInt(), any());
+        verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
+
+        // Network disconnects
+        mManager.clearNotification(id);
+        verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 652c54e..56cc1dc 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -16,6 +16,11 @@
 
 package com.android.server.connectivity;
 
+import static android.hardware.usb.UsbManager.USB_CONFIGURED;
+import static android.hardware.usb.UsbManager.USB_CONNECTED;
+import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.ConnectivityManager.TETHERING_WIFI;
+import static android.net.ConnectivityManager.TETHERING_USB;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
@@ -136,6 +141,7 @@
         public Object getSystemService(String name) {
             if (Context.CONNECTIVITY_SERVICE.equals(name)) return mConnectivityManager;
             if (Context.WIFI_SERVICE.equals(name)) return mWifiManager;
+            if (Context.USB_SERVICE.equals(name)) return mUsbManager;
             return super.getSystemService(name);
         }
     }
@@ -148,7 +154,7 @@
         when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
                 .thenReturn(new String[0]);
         when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
-                .thenReturn(new String[]{ "test_wlan\\d" });
+                .thenReturn(new String[]{ "test_wlan\\d", "test_rndis\\d" });
         when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
                 .thenReturn(new String[0]);
         when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
@@ -175,6 +181,7 @@
         mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
                                    mLooper.getLooper(), mSystemProperties,
                                    mTetheringDependencies);
+        verify(mNMService).registerTetheringStatsProvider(any(), anyString());
     }
 
     @After
@@ -248,6 +255,14 @@
         mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
+    private void sendUsbBroadcast(boolean connected, boolean configured, boolean rndisFunction) {
+        final Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
+        intent.putExtra(USB_CONNECTED, connected);
+        intent.putExtra(USB_CONFIGURED, configured);
+        intent.putExtra(USB_FUNCTION_RNDIS, rndisFunction);
+        mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
     private void verifyInterfaceServingModeStarted(boolean ifnameKnown) throws Exception {
         if (!ifnameKnown) {
             verify(mNMService, times(1)).listInterfaces();
@@ -267,6 +282,32 @@
         mIntents.remove(bcast);
     }
 
+    @Test
+    public void testUsbConfiguredBroadcastStartsTethering() throws Exception {
+        when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
+
+        // Emulate pressing the USB tethering button in Settings UI.
+        mTethering.startTethering(TETHERING_USB, null, false);
+        mLooper.dispatchAll();
+        verify(mUsbManager, times(1)).setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false);
+
+        // Pretend we receive a USB connected broadcast. Here we also pretend
+        // that the RNDIS function is somehow enabled, so that we see if we
+        // might trip ourselves up.
+        sendUsbBroadcast(true, false, true);
+        mLooper.dispatchAll();
+        // This should produce no activity of any kind.
+        verifyNoMoreInteractions(mConnectivityManager);
+        verifyNoMoreInteractions(mNMService);
+
+        // Pretend we then receive USB configured broadcast.
+        sendUsbBroadcast(true, true, true);
+        mLooper.dispatchAll();
+        // Now we should see the start of tethering mechanics (in this case:
+        // tetherMatchingInterfaces() which starts by fetching all interfaces).
+        verify(mNMService, times(1)).listInterfaces();
+    }
+
     public void workingLocalOnlyHotspot(
             boolean withInterfaceStateChanged, boolean enrichedApBroadcast) throws Exception {
         when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
@@ -344,7 +385,7 @@
         when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
 
         // Emulate pressing the WiFi tethering button.
-        mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
+        mTethering.startTethering(TETHERING_WIFI, null, false);
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startSoftAp(null);
         verifyNoMoreInteractions(mWifiManager);
@@ -394,7 +435,7 @@
         /////
 
         // Emulate pressing the WiFi tethering button.
-        mTethering.stopTethering(ConnectivityManager.TETHERING_WIFI);
+        mTethering.stopTethering(TETHERING_WIFI);
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).stopSoftAp();
         verifyNoMoreInteractions(mWifiManager);
@@ -439,7 +480,7 @@
         doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true);
 
         // Emulate pressing the WiFi tethering button.
-        mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
+        mTethering.startTethering(TETHERING_WIFI, null, false);
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startSoftAp(null);
         verifyNoMoreInteractions(mWifiManager);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 1ddaf66..45525e6 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -16,25 +16,38 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.net.ITetheringStatsProvider;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkStats;
 import android.net.RouteInfo;
 import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.INetworkManagementService;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 
@@ -45,7 +58,10 @@
 
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.junit.Test;
@@ -62,38 +78,84 @@
     @Mock private OffloadHardwareInterface mHardware;
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
+    @Mock private INetworkManagementService mNMService;
     private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
             ArgumentCaptor.forClass(ArrayList.class);
+    private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
+            ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
     private MockContentResolver mContentResolver;
 
-    @Before public void setUp() throws Exception {
+    @Before public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
         when(mContext.getPackageName()).thenReturn("OffloadControllerTest");
         mContentResolver = new MockContentResolver(mContext);
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        // TODO: call this when available.
+        // FakeSettingsProvider.clearSettingsProvider();
+    }
+
+    @After public void tearDown() throws Exception {
+        // TODO: call this when available.
+        // FakeSettingsProvider.clearSettingsProvider();
     }
 
     private void setupFunctioningHardwareInterface() {
         when(mHardware.initOffloadConfig()).thenReturn(true);
         when(mHardware.initOffloadControl(any(OffloadHardwareInterface.ControlCallback.class)))
                 .thenReturn(true);
+        when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
     }
 
-    @Test
-    public void testNoSettingsValueAllowsStart() {
+    private void enableOffload() {
+        Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
+    }
+
+    private OffloadController makeOffloadController() throws Exception {
+        OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
+                mHardware, mContentResolver, mNMService, new SharedLog("test"));
+        verify(mNMService).registerTetheringStatsProvider(
+                mTetherStatsProviderCaptor.capture(), anyString());
+        return offload;
+    }
+
+    // TODO: Restore when FakeSettingsProvider.clearSettingsProvider() is available.
+    // @Test
+    public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception {
         setupFunctioningHardwareInterface();
+        when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
         try {
             Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED);
             fail();
         } catch (SettingNotFoundException expected) {}
 
-        final OffloadController offload =
-                new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+        final OffloadController offload = makeOffloadController();
         offload.start();
 
         final InOrder inOrder = inOrder(mHardware);
+        inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
+        inOrder.verify(mHardware, never()).initOffloadConfig();
+        inOrder.verify(mHardware, never()).initOffloadControl(
+                any(OffloadHardwareInterface.ControlCallback.class));
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    // TODO: Restore when FakeSettingsProvider.clearSettingsProvider() is available.
+    // @Test
+    public void testNoSettingsValueDefaultEnabledDoesStart() throws Exception {
+        setupFunctioningHardwareInterface();
+        when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0);
+        try {
+            Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED);
+            fail();
+        } catch (SettingNotFoundException expected) {}
+
+        final OffloadController offload = makeOffloadController();
+        offload.start();
+
+        final InOrder inOrder = inOrder(mHardware);
+        inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
         inOrder.verify(mHardware, times(1)).initOffloadConfig();
         inOrder.verify(mHardware, times(1)).initOffloadControl(
                 any(OffloadHardwareInterface.ControlCallback.class));
@@ -101,15 +163,15 @@
     }
 
     @Test
-    public void testSettingsAllowsStart() {
+    public void testSettingsAllowsStart() throws Exception {
         setupFunctioningHardwareInterface();
         Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
 
-        final OffloadController offload =
-                new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+        final OffloadController offload = makeOffloadController();
         offload.start();
 
         final InOrder inOrder = inOrder(mHardware);
+        inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
         inOrder.verify(mHardware, times(1)).initOffloadConfig();
         inOrder.verify(mHardware, times(1)).initOffloadControl(
                 any(OffloadHardwareInterface.ControlCallback.class));
@@ -117,15 +179,15 @@
     }
 
     @Test
-    public void testSettingsDisablesStart() {
+    public void testSettingsDisablesStart() throws Exception {
         setupFunctioningHardwareInterface();
         Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
 
-        final OffloadController offload =
-                new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+        final OffloadController offload = makeOffloadController();
         offload.start();
 
         final InOrder inOrder = inOrder(mHardware);
+        inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
         inOrder.verify(mHardware, never()).initOffloadConfig();
         inOrder.verify(mHardware, never()).initOffloadControl(anyObject());
         inOrder.verifyNoMoreInteractions();
@@ -134,27 +196,55 @@
     @Test
     public void testSetUpstreamLinkPropertiesWorking() throws Exception {
         setupFunctioningHardwareInterface();
-        final OffloadController offload =
-                new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+        enableOffload();
+
+        final OffloadController offload = makeOffloadController();
         offload.start();
 
         final InOrder inOrder = inOrder(mHardware);
+        inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
         inOrder.verify(mHardware, times(1)).initOffloadConfig();
         inOrder.verify(mHardware, times(1)).initOffloadControl(
                 any(OffloadHardwareInterface.ControlCallback.class));
         inOrder.verifyNoMoreInteractions();
 
-        offload.setUpstreamLinkProperties(null);
-        inOrder.verify(mHardware, times(1)).setUpstreamParameters(
-                eq(null), eq(null), eq(null), eq(null));
+        // In reality, the UpstreamNetworkMonitor would have passed down to us
+        // a covering set of local prefixes representing a minimum essential
+        // set plus all the prefixes on networks with network agents.
+        //
+        // We simulate that there, and then add upstream elements one by one
+        // and watch what happens.
+        final Set<IpPrefix> minimumLocalPrefixes = new HashSet<>();
+        for (String s : new String[]{
+                "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64"}) {
+            minimumLocalPrefixes.add(new IpPrefix(s));
+        }
+        offload.setLocalPrefixes(minimumLocalPrefixes);
+        inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
+        ArrayList<String> localPrefixes = mStringArrayCaptor.getValue();
+        assertEquals(4, localPrefixes.size());
+        assertTrue(localPrefixes.contains("127.0.0.0/8"));
+        assertTrue(localPrefixes.contains("192.0.2.0/24"));
+        assertTrue(localPrefixes.contains("fe80::/64"));
+        assertTrue(localPrefixes.contains("2001:db8::/64"));
         inOrder.verifyNoMoreInteractions();
-        reset(mHardware);
+
+        offload.setUpstreamLinkProperties(null);
+        // No change in local addresses means no call to setLocalPrefixes().
+        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
+        // This LinkProperties value does not differ from the default upstream.
+        // There should be no extraneous call to setUpstreamParameters().
+        inOrder.verify(mHardware, never()).setUpstreamParameters(
+                anyObject(), anyObject(), anyObject(), anyObject());
+        inOrder.verifyNoMoreInteractions();
 
         final LinkProperties lp = new LinkProperties();
 
         final String testIfName = "rmnet_data17";
         lp.setInterfaceName(testIfName);
         offload.setUpstreamLinkProperties(lp);
+        // No change in local addresses means no call to setLocalPrefixes().
+        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                 eq(testIfName), eq(null), eq(null), eq(null));
         inOrder.verifyNoMoreInteractions();
@@ -162,23 +252,38 @@
         final String ipv4Addr = "192.0.2.5";
         final String linkAddr = ipv4Addr + "/24";
         lp.addLinkAddress(new LinkAddress(linkAddr));
+        lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24")));
         offload.setUpstreamLinkProperties(lp);
+        // IPv4 prefixes and addresses on the upstream are simply left as whole
+        // prefixes (already passed in from UpstreamNetworkMonitor code). If a
+        // tethering client sends traffic to the IPv4 default router or other
+        // clients on the upstream this will not be hardware-forwarded, and that
+        // should be fine for now. Ergo: no change in local addresses, no call
+        // to setLocalPrefixes().
+        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                 eq(testIfName), eq(ipv4Addr), eq(null), eq(null));
+        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
         inOrder.verifyNoMoreInteractions();
 
         final String ipv4Gateway = "192.0.2.1";
         lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway)));
         offload.setUpstreamLinkProperties(lp);
+        // No change in local addresses means no call to setLocalPrefixes().
+        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                 eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), eq(null));
+        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
         inOrder.verifyNoMoreInteractions();
 
         final String ipv6Gw1 = "fe80::cafe";
         lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw1)));
         offload.setUpstreamLinkProperties(lp);
+        // No change in local addresses means no call to setLocalPrefixes().
+        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                 eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
         ArrayList<String> v6gws = mStringArrayCaptor.getValue();
         assertEquals(1, v6gws.size());
         assertTrue(v6gws.contains(ipv6Gw1));
@@ -187,8 +292,11 @@
         final String ipv6Gw2 = "fe80::d00d";
         lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw2)));
         offload.setUpstreamLinkProperties(lp);
+        // No change in local addresses means no call to setLocalPrefixes().
+        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                 eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
         v6gws = mStringArrayCaptor.getValue();
         assertEquals(2, v6gws.size());
         assertTrue(v6gws.contains(ipv6Gw1));
@@ -202,12 +310,115 @@
         stacked.addRoute(new RouteInfo(InetAddress.getByName("fe80::bad:f00")));
         assertTrue(lp.addStackedLink(stacked));
         offload.setUpstreamLinkProperties(lp);
+        // No change in local addresses means no call to setLocalPrefixes().
+        inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
+        inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+                eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
+        v6gws = mStringArrayCaptor.getValue();
+        assertEquals(2, v6gws.size());
+        assertTrue(v6gws.contains(ipv6Gw1));
+        assertTrue(v6gws.contains(ipv6Gw2));
+        inOrder.verifyNoMoreInteractions();
+
+        // Add in some IPv6 upstream info. When there is a tethered downstream
+        // making use of the IPv6 prefix we would expect to see the /64 route
+        // removed from "local prefixes" and /128s added for the upstream IPv6
+        // addresses.  This is not yet implemented, and for now we simply
+        // expect to see these /128s.
+        lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64")));
+        // "2001:db8::/64" plus "assigned" ASCII in hex
+        lp.addLinkAddress(new LinkAddress("2001:db8::6173:7369:676e:6564/64"));
+        // "2001:db8::/64" plus "random" ASCII in hex
+        lp.addLinkAddress(new LinkAddress("2001:db8::7261:6e64:6f6d/64"));
+        offload.setUpstreamLinkProperties(lp);
+        inOrder.verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
+        localPrefixes = mStringArrayCaptor.getValue();
+        assertEquals(6, localPrefixes.size());
+        assertTrue(localPrefixes.contains("127.0.0.0/8"));
+        assertTrue(localPrefixes.contains("192.0.2.0/24"));
+        assertTrue(localPrefixes.contains("fe80::/64"));
+        assertTrue(localPrefixes.contains("2001:db8::/64"));
+        assertTrue(localPrefixes.contains("2001:db8::6173:7369:676e:6564/128"));
+        assertTrue(localPrefixes.contains("2001:db8::7261:6e64:6f6d/128"));
+        // The relevant parts of the LinkProperties have not changed, but at the
+        // moment we do not de-dup upstream LinkProperties this carefully.
         inOrder.verify(mHardware, times(1)).setUpstreamParameters(
                 eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
         v6gws = mStringArrayCaptor.getValue();
         assertEquals(2, v6gws.size());
         assertTrue(v6gws.contains(ipv6Gw1));
         assertTrue(v6gws.contains(ipv6Gw2));
+        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
         inOrder.verifyNoMoreInteractions();
+
+        // Completely identical LinkProperties updates are de-duped.
+        offload.setUpstreamLinkProperties(lp);
+        // This LinkProperties value does not differ from the default upstream.
+        // There should be no extraneous call to setUpstreamParameters().
+        inOrder.verify(mHardware, never()).setUpstreamParameters(
+                anyObject(), anyObject(), anyObject(), anyObject());
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
+        assertEquals(iface, entry.iface);
+        assertEquals(stats.rxBytes, entry.rxBytes);
+        assertEquals(stats.txBytes, entry.txBytes);
+        assertEquals(SET_DEFAULT, entry.set);
+        assertEquals(TAG_NONE, entry.tag);
+        assertEquals(UID_TETHERING, entry.uid);
+    }
+
+    @Test
+    public void testGetForwardedStats() throws Exception {
+        setupFunctioningHardwareInterface();
+        enableOffload();
+
+        final OffloadController offload = makeOffloadController();
+        offload.start();
+
+        final String ethernetIface = "eth1";
+        final String mobileIface = "rmnet_data0";
+
+        ForwardedStats ethernetStats = new ForwardedStats();
+        ethernetStats.rxBytes = 12345;
+        ethernetStats.txBytes = 54321;
+
+        ForwardedStats mobileStats = new ForwardedStats();
+        mobileStats.rxBytes = 999;
+        mobileStats.txBytes = 99999;
+
+        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
+        when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
+
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(ethernetIface);
+        offload.setUpstreamLinkProperties(lp);
+
+        lp.setInterfaceName(mobileIface);
+        offload.setUpstreamLinkProperties(lp);
+
+        lp.setInterfaceName(ethernetIface);
+        offload.setUpstreamLinkProperties(lp);
+
+        ethernetStats.rxBytes = 100000;
+        ethernetStats.txBytes = 100000;
+        offload.setUpstreamLinkProperties(null);
+
+        NetworkStats stats = mTetherStatsProviderCaptor.getValue().getTetherStats();
+        assertEquals(2, stats.size());
+
+        NetworkStats.Entry entry = null;
+        int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
+        int mobilePosition = 1 - ethernetPosition;
+
+        entry = stats.getValues(mobilePosition, entry);
+        assertNetworkStats(mobileIface, mobileStats, entry);
+
+        ethernetStats.rxBytes = 12345 + 100000;
+        ethernetStats.txBytes = 54321 + 100000;
+        entry = stats.getValues(ethernetPosition, entry);
+        assertNetworkStats(ethernetIface, ethernetStats, entry);
     }
 }
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index fb5c577..c3b9def 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -44,6 +44,9 @@
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.IConnectivityManager;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
@@ -66,6 +69,7 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -77,6 +81,9 @@
 public class UpstreamNetworkMonitorTest {
     private static final int EVENT_UNM_UPDATE = 1;
 
+    private static final boolean INCLUDES = true;
+    private static final boolean EXCLUDES = false;
+
     @Mock private Context mContext;
     @Mock private IConnectivityManager mCS;
     @Mock private SharedLog mLog;
@@ -94,7 +101,8 @@
 
         mCM = spy(new TestConnectivityManager(mContext, mCS));
         mSM = new TestStateMachine();
-        mUNM = new UpstreamNetworkMonitor(mSM, EVENT_UNM_UPDATE, (ConnectivityManager) mCM, mLog);
+        mUNM = new UpstreamNetworkMonitor(
+                (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE);
     }
 
     @After public void tearDown() throws Exception {
@@ -315,6 +323,95 @@
         assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
     }
 
+    @Test
+    public void testLocalPrefixes() throws Exception {
+        mUNM.start();
+
+        // [0] Test minimum set of local prefixes.
+        Set<IpPrefix> local = mUNM.getLocalPrefixes();
+        assertTrue(local.isEmpty());
+
+        final Set<String> alreadySeen = new HashSet<>();
+
+        // [1] Pretend Wi-Fi connects.
+        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName("wlan0");
+        final String[] WIFI_ADDRS = {
+                "fe80::827a:bfff:fe6f:374d", "100.112.103.18",
+                "2001:db8:4:fd00:827a:bfff:fe6f:374d",
+                "2001:db8:4:fd00:6dea:325a:fdae:4ef4",
+                "fd6a:a640:60bf:e985::123",  // ULA address for good measure.
+        };
+        for (String addrStr : WIFI_ADDRS) {
+            final String cidr = addrStr.contains(":") ? "/64" : "/20";
+            wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr));
+        }
+        wifiAgent.fakeConnect();
+        wifiAgent.sendLinkProperties(wifiLp);
+
+        local = mUNM.getLocalPrefixes();
+        assertPrefixSet(local, INCLUDES, alreadySeen);
+        final String[] wifiLinkPrefixes = {
+                // Link-local prefixes are excluded and dealt with elsewhere.
+                "100.112.96.0/20", "2001:db8:4:fd00::/64", "fd6a:a640:60bf:e985::/64",
+        };
+        assertPrefixSet(local, INCLUDES, wifiLinkPrefixes);
+        Collections.addAll(alreadySeen, wifiLinkPrefixes);
+        assertEquals(alreadySeen.size(), local.size());
+
+        // [2] Pretend mobile connects.
+        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        final LinkProperties cellLp = new LinkProperties();
+        cellLp.setInterfaceName("rmnet_data0");
+        final String[] CELL_ADDRS = {
+                "10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d",
+        };
+        for (String addrStr : CELL_ADDRS) {
+            final String cidr = addrStr.contains(":") ? "/64" : "/27";
+            cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
+        }
+        cellAgent.fakeConnect();
+        cellAgent.sendLinkProperties(cellLp);
+
+        local = mUNM.getLocalPrefixes();
+        assertPrefixSet(local, INCLUDES, alreadySeen);
+        final String[] cellLinkPrefixes = { "10.102.211.32/27", "2001:db8:0:1::/64" };
+        assertPrefixSet(local, INCLUDES, cellLinkPrefixes);
+        Collections.addAll(alreadySeen, cellLinkPrefixes);
+        assertEquals(alreadySeen.size(), local.size());
+
+        // [3] Pretend DUN connects.
+        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
+        final LinkProperties dunLp = new LinkProperties();
+        dunLp.setInterfaceName("rmnet_data1");
+        final String[] DUN_ADDRS = {
+                "192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d",
+        };
+        for (String addrStr : DUN_ADDRS) {
+            final String cidr = addrStr.contains(":") ? "/64" : "/27";
+            cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
+        }
+        dunAgent.fakeConnect();
+        dunAgent.sendLinkProperties(dunLp);
+
+        local = mUNM.getLocalPrefixes();
+        assertPrefixSet(local, INCLUDES, alreadySeen);
+        final String[] dunLinkPrefixes = { "192.0.2.32/27", "2001:db8:1:2::/64" };
+        assertPrefixSet(local, INCLUDES, dunLinkPrefixes);
+        Collections.addAll(alreadySeen, dunLinkPrefixes);
+        assertEquals(alreadySeen.size(), local.size());
+
+        // [4] Pretend Wi-Fi disconnected.  It's addresses/prefixes should no
+        // longer be included (should be properly removed).
+        wifiAgent.fakeDisconnect();
+        local = mUNM.getLocalPrefixes();
+        assertPrefixSet(local, EXCLUDES, wifiLinkPrefixes);
+        assertPrefixSet(local, INCLUDES, cellLinkPrefixes);
+        assertPrefixSet(local, INCLUDES, dunLinkPrefixes);
+    }
+
     private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) {
         if (legacyType == TYPE_NONE) {
             assertTrue(ns == null);
@@ -476,6 +573,12 @@
                 cb.onLost(networkId);
             }
         }
+
+        public void sendLinkProperties(LinkProperties lp) {
+            for (NetworkCallback cb : cm.listening.keySet()) {
+                cb.onLinkPropertiesChanged(networkId, lp);
+            }
+        }
     }
 
     public static class TestStateMachine extends StateMachine {
@@ -504,4 +607,19 @@
     static NetworkCapabilities copy(NetworkCapabilities nc) {
         return new NetworkCapabilities(nc);
     }
+
+    static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
+        final Set<String> expectedSet = new HashSet<>();
+        Collections.addAll(expectedSet, expected);
+        assertPrefixSet(prefixes, expectation, expectedSet);
+    }
+
+    static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, Set<String> expected) {
+        for (String expectedPrefix : expected) {
+            final String errStr = expectation ? "did not find" : "found";
+            assertEquals(
+                    String.format("Failed expectation: %s prefix: %s", errStr, expectedPrefix),
+                    expectation, prefixes.contains(new IpPrefix(expectedPrefix)));
+        }
+    }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index 92dcdac..2be5dae 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -27,6 +27,8 @@
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+import static com.android.internal.util.TestUtils.waitForIdleHandler;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -56,7 +58,6 @@
 
 import com.android.internal.net.VpnInfo;
 import com.android.server.net.NetworkStatsService;
-import com.android.server.net.NetworkStatsServiceTest.IdleableHandlerThread;
 import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
 
 import java.util.ArrayList;
@@ -102,7 +103,7 @@
 
     private long mElapsedRealtime;
 
-    private IdleableHandlerThread mObserverHandlerThread;
+    private HandlerThread mObserverHandlerThread;
     private Handler mObserverNoopHandler;
 
     private LatchedHandler mHandler;
@@ -118,7 +119,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mObserverHandlerThread = new IdleableHandlerThread("HandlerThread");
+        mObserverHandlerThread = new HandlerThread("HandlerThread");
         mObserverHandlerThread.start();
         final Looper observerLooper = mObserverHandlerThread.getLooper();
         mStatsObservers = new NetworkStatsObservers() {
@@ -319,7 +320,7 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
     }
 
     @Test
@@ -356,7 +357,7 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
     }
 
     @Test
@@ -429,7 +430,7 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
     }
 
     @Test
@@ -470,19 +471,7 @@
     }
 
     private void waitForObserverToIdle() {
-        waitForIdleLooper(mObserverHandlerThread.getLooper(), WAIT_TIMEOUT_MS);
-        waitForIdleLooper(mHandler.getLooper(), WAIT_TIMEOUT_MS);
+        waitForIdleHandler(mObserverHandlerThread, WAIT_TIMEOUT_MS);
+        waitForIdleHandler(mHandler, WAIT_TIMEOUT_MS);
     }
-
-    // TODO: unify with ConnectivityService.waitForIdleHandler and
-    // NetworkServiceStatsTest.IdleableHandlerThread
-    private static void waitForIdleLooper(Looper looper, long timeoutMs) {
-        final ConditionVariable cv = new ConditionVariable();
-        final Handler handler = new Handler(looper);
-        handler.post(() -> cv.open());
-        if (!cv.block(timeoutMs)) {
-            fail("Looper did not become idle after " + timeoutMs + " ms");
-        }
-    }
-
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 029693f..feb46d3 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -45,6 +45,7 @@
 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static com.android.internal.util.TestUtils.waitForIdleHandler;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -154,7 +155,7 @@
     private @Mock IConnectivityManager mConnManager;
     private @Mock IBinder mBinder;
     private @Mock AlarmManager mAlarmManager;
-    private IdleableHandlerThread mHandlerThread;
+    private HandlerThread mHandlerThread;
     private Handler mHandler;
 
     private NetworkStatsService mService;
@@ -181,7 +182,7 @@
                 mServiceContext, mNetManager, mAlarmManager, wakeLock, mTime,
                 TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(),
                 mStatsDir, getBaseDir(mStatsDir));
-        mHandlerThread = new IdleableHandlerThread("HandlerThread");
+        mHandlerThread = new HandlerThread("HandlerThread");
         mHandlerThread.start();
         Handler.Callback callback = new NetworkStatsService.HandlerCallback(mService);
         mHandler = new Handler(mHandlerThread.getLooper(), callback);
@@ -886,7 +887,7 @@
 
         // Send dummy message to make sure that any previous message has been handled
         mHandler.sendMessage(mHandler.obtainMessage(-1));
-        mHandlerThread.waitForIdle(WAIT_TIMEOUT);
+        waitForIdleHandler(mHandler, WAIT_TIMEOUT);
 
 
 
@@ -908,7 +909,7 @@
         assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
 
         // make sure callback has not being called
-        assertEquals(INVALID_TYPE, latchedHandler.mLastMessageType);
+        assertEquals(INVALID_TYPE, latchedHandler.lastMessageType);
 
         // and bump forward again, with counters going higher. this is
         // important, since it will trigger the data usage callback
@@ -926,7 +927,7 @@
 
         // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
         assertTrue(cv.block(WAIT_TIMEOUT));
-        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.mLastMessageType);
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.lastMessageType);
         cv.close();
 
         // Allow binder to disconnect
@@ -937,7 +938,7 @@
 
         // Wait for the caller to ack receipt of CALLBACK_RELEASED
         assertTrue(cv.block(WAIT_TIMEOUT));
-        assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.mLastMessageType);
+        assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.lastMessageType);
 
         // Make sure that the caller binder gets disconnected
         verify(mBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
@@ -1203,12 +1204,12 @@
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
         // Send dummy message to make sure that any previous message has been handled
         mHandler.sendMessage(mHandler.obtainMessage(-1));
-        mHandlerThread.waitForIdle(WAIT_TIMEOUT);
+        waitForIdleHandler(mHandler, WAIT_TIMEOUT);
     }
 
     static class LatchedHandler extends Handler {
         private final ConditionVariable mCv;
-        int mLastMessageType = INVALID_TYPE;
+        int lastMessageType = INVALID_TYPE;
 
         LatchedHandler(Looper looper, ConditionVariable cv) {
             super(looper);
@@ -1217,49 +1218,9 @@
 
         @Override
         public void handleMessage(Message msg) {
-            mLastMessageType = msg.what;
+            lastMessageType = msg.what;
             mCv.open();
             super.handleMessage(msg);
         }
     }
-
-    /**
-     * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
-     * will return immediately if the handler is already idle.
-     */
-    static class IdleableHandlerThread extends HandlerThread {
-        private IdleHandler mIdleHandler;
-
-        public IdleableHandlerThread(String name) {
-            super(name);
-        }
-
-        public void waitForIdle(long timeoutMs) {
-            final ConditionVariable cv = new ConditionVariable();
-            final MessageQueue queue = getLooper().getQueue();
-
-            synchronized (queue) {
-                if (queue.isIdle()) {
-                    return;
-                }
-
-                assertNull("BUG: only one idle handler allowed", mIdleHandler);
-                mIdleHandler = new IdleHandler() {
-                    public boolean queueIdle() {
-                        cv.open();
-                        mIdleHandler = null;
-                        return false;  // Remove the handler.
-                    }
-                };
-                queue.addIdleHandler(mIdleHandler);
-            }
-
-            if (!cv.block(timeoutMs)) {
-                fail("HandlerThread " + getName() + " did not become idle after " + timeoutMs
-                        + " ms");
-                queue.removeIdleHandler(mIdleHandler);
-            }
-        }
-    }
-
 }
diff --git a/tests/net/jni/apf_jni.cpp b/tests/net/jni/apf_jni.cpp
index ee43dd4..82cbb6b 100644
--- a/tests/net/jni/apf_jni.cpp
+++ b/tests/net/jni/apf_jni.cpp
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#include <JNIHelp.h>
-#include <ScopedUtfChars.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <jni.h>
 #include <pcap.h>
 #include <stdlib.h>