Merge "Consider the last inserted SIM for EmergencyAffordance"
diff --git a/Android.bp b/Android.bp
index dba49ce..6fc0aa9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13,7 +13,9 @@
 // limitations under the License.
 
 subdirs = [
+    "core/jni",
     "libs/*",
+    "media/*",
     "native/android",
     "native/graphics/jni",
 ]
diff --git a/Android.mk b/Android.mk
index 1aaa09a..435d571 100644
--- a/Android.mk
+++ b/Android.mk
@@ -103,6 +103,8 @@
 	core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreSession.aidl \
+	core/java/android/app/timezone/ICallback.aidl \
+	core/java/android/app/timezone/IRulesManager.aidl \
 	core/java/android/app/usage/IUsageStatsManager.aidl \
 	core/java/android/bluetooth/IBluetooth.aidl \
 	core/java/android/bluetooth/IBluetoothA2dp.aidl \
@@ -130,7 +132,6 @@
 	core/java/android/bluetooth/IBluetoothGatt.aidl \
 	core/java/android/bluetooth/IBluetoothGattCallback.aidl \
 	core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \
-	core/java/android/bluetooth/le/IAdvertiserCallback.aidl \
 	core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl \
 	core/java/android/bluetooth/le/IPeriodicAdvertisingCallback.aidl \
 	core/java/android/bluetooth/le/IScannerCallback.aidl \
@@ -404,6 +405,7 @@
 	media/java/android/media/projection/IMediaProjectionManager.aidl \
 	media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl \
 	media/java/android/media/session/IActiveSessionsListener.aidl \
+	media/java/android/media/session/ICallback.aidl \
 	media/java/android/media/session/ISessionController.aidl \
 	media/java/android/media/session/ISessionControllerCallback.aidl \
 	media/java/android/media/session/ISession.aidl \
@@ -432,6 +434,12 @@
 	telecomm/java/com/android/internal/telecom/IInCallService.aidl \
 	telecomm/java/com/android/internal/telecom/ITelecomService.aidl \
 	telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl \
+        telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl \
+	telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl \
+	telephony/java/android/telephony/mbms/IDownloadCallback.aidl \
+        telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl \
+	telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl \
+	telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl \
 	telephony/java/com/android/ims/internal/IImsCallSession.aidl \
 	telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl \
 	telephony/java/com/android/ims/internal/IImsConfig.aidl \
@@ -540,6 +548,12 @@
 include $(CLEAR_VARS)
 
 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 \
+        frameworks/base/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl \
 	frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
 	frameworks/base/telephony/java/android/telephony/SubscriptionInfo.aidl \
 	frameworks/base/telephony/java/android/telephony/CellInfo.aidl \
@@ -758,7 +772,6 @@
 include libcore/Docs.mk
 
 non_base_dirs := \
-	../opt/telephony/src/java/android/provider \
 	../opt/telephony/src/java/android/telephony \
 	../opt/telephony/src/java/android/telephony/gsm \
 	../opt/net/voip/src/java/android/net/rtp \
diff --git a/api/current.txt b/api/current.txt
index 1a6ae5c..4275e9d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -36,6 +36,7 @@
     field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
     field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
+    field public static final java.lang.String BIND_VISUAL_VOICEMAIL_SERVICE = "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE";
     field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION";
     field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
     field public static final java.lang.String BIND_VR_LISTENER_SERVICE = "android.permission.BIND_VR_LISTENER_SERVICE";
@@ -7077,6 +7078,7 @@
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int);
+    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int, android.os.Handler);
     method public boolean createBond();
     method public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
     method public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
@@ -7121,9 +7123,11 @@
     field public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; // 0x2
     field public static final int PAIRING_VARIANT_PIN = 0; // 0x0
     field public static final int PHY_LE_1M = 1; // 0x1
+    field public static final int PHY_LE_1M_MASK = 1; // 0x1
     field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_ANY = 7; // 0x7
-    field public static final int PHY_LE_CODED = 4; // 0x4
+    field public static final int PHY_LE_2M_MASK = 2; // 0x2
+    field public static final int PHY_LE_CODED = 3; // 0x3
+    field public static final int PHY_LE_CODED_MASK = 4; // 0x4
     field public static final int PHY_OPTION_NO_PREFERRED = 0; // 0x0
     field public static final int PHY_OPTION_S2 = 1; // 0x1
     field public static final int PHY_OPTION_S8 = 2; // 0x2
@@ -7499,7 +7503,7 @@
     method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
     method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
     method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
-    method public void setPeriodicAdvertisingEnable(boolean);
+    method public void setPeriodicAdvertisingEnabled(boolean);
     method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
     method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
   }
@@ -7536,14 +7540,11 @@
     method public boolean isScannable();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
-    field public static final int INTERVAL_HIGH = 160; // 0xa0
-    field public static final int INTERVAL_LOW = 1600; // 0x640
+    field public static final int INTERVAL_HIGH = 1600; // 0x640
+    field public static final int INTERVAL_LOW = 160; // 0xa0
     field public static final int INTERVAL_MAX = 16777215; // 0xffffff
     field public static final int INTERVAL_MEDIUM = 400; // 0x190
     field public static final int INTERVAL_MIN = 160; // 0xa0
-    field public static final int PHY_LE_1M = 1; // 0x1
-    field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_CODED = 3; // 0x3
     field public static final int TX_POWER_HIGH = 1; // 0x1
     field public static final int TX_POWER_LOW = -15; // 0xfffffff1
     field public static final int TX_POWER_MAX = 1; // 0x1
@@ -7586,7 +7587,6 @@
 
   public final class PeriodicAdvertisingParameters implements android.os.Parcelable {
     method public int describeContents();
-    method public boolean getEnable();
     method public boolean getIncludeTxPower();
     method public int getInterval();
     method public void writeToParcel(android.os.Parcel, int);
@@ -7596,7 +7596,6 @@
   public static final class PeriodicAdvertisingParameters.Builder {
     ctor public PeriodicAdvertisingParameters.Builder();
     method public android.bluetooth.le.PeriodicAdvertisingParameters build();
-    method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setEnable(boolean);
     method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setIncludeTxPower(boolean);
     method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setInterval(int);
   }
@@ -7674,11 +7673,10 @@
     field public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanResult> CREATOR;
     field public static final int DATA_COMPLETE = 0; // 0x0
     field public static final int DATA_TRUNCATED = 2; // 0x2
-    field public static final int PHY_LE_1M = 1; // 0x1
-    field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_CODED = 3; // 0x3
+    field public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0; // 0x0
     field public static final int PHY_UNUSED = 0; // 0x0
     field public static final int SID_NOT_PRESENT = 255; // 0xff
+    field public static final int TX_POWER_NOT_PRESENT = 127; // 0x7f
   }
 
   public final class ScanSettings implements android.os.Parcelable {
@@ -7699,9 +7697,7 @@
     field public static final int MATCH_NUM_FEW_ADVERTISEMENT = 2; // 0x2
     field public static final int MATCH_NUM_MAX_ADVERTISEMENT = 3; // 0x3
     field public static final int MATCH_NUM_ONE_ADVERTISEMENT = 1; // 0x1
-    field public static final int PHY_LE_1M = 1; // 0x1
     field public static final int PHY_LE_ALL_SUPPORTED = 255; // 0xff
-    field public static final int PHY_LE_CODED = 3; // 0x3
     field public static final int SCAN_MODE_BALANCED = 1; // 0x1
     field public static final int SCAN_MODE_LOW_LATENCY = 2; // 0x2
     field public static final int SCAN_MODE_LOW_POWER = 0; // 0x0
@@ -8326,7 +8322,6 @@
     field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
     field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
     field public static final java.lang.String INPUT_SERVICE = "input";
-    field public static final java.lang.String IPSEC_SERVICE = "ipsec";
     field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
     field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
     field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -18187,9 +18182,9 @@
     method protected final void computeGregorianFields(int);
     method protected int computeGregorianMonthStart(int, int);
     method protected int computeJulianDay();
-    method protected int computeMillisInDay();
+    method protected deprecated int computeMillisInDay();
     method protected void computeTime();
-    method protected int computeZoneOffset(long, int);
+    method protected deprecated int computeZoneOffset(long, int);
     method public int fieldDifference(java.util.Date, int);
     method protected java.lang.String fieldName(int);
     method protected static final long floorDivide(long, long);
@@ -23789,8 +23784,8 @@
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
-    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback);
-    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
+    method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, int);
+    method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler, int);
     method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
     method public deprecated boolean requestRouteToHost(int, int);
     method public deprecated void setNetworkPreference(int);
@@ -23878,67 +23873,6 @@
     field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
   }
 
-  public final class IpSecAlgorithm implements android.os.Parcelable {
-    ctor public IpSecAlgorithm(java.lang.String, byte[]);
-    ctor public IpSecAlgorithm(java.lang.String, byte[], int);
-    method public int describeContents();
-    method public byte[] getKey();
-    method public java.lang.String getName();
-    method public int getTruncationLengthBits();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final java.lang.String ALGO_AUTH_HMAC_MD5 = "hmac(md5)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)";
-    field public static final java.lang.String ALGO_CRYPT_AES_CBC = "cbc(aes)";
-    field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
-  }
-
-  public final class IpSecManager {
-    method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
-    method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
-    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
-    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
-    method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
-    method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
-    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
-  }
-
-  public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
-  }
-
-  public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
-    method public void close();
-    method public int getSpi();
-  }
-
-  public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
-    method public int getSpi();
-  }
-
-  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
-    method public void close();
-    method public int getPort();
-    method public java.io.FileDescriptor getSocket();
-  }
-
-  public final class IpSecTransform implements java.lang.AutoCloseable {
-    method public void close();
-    field public static final int DIRECTION_IN = 0; // 0x0
-    field public static final int DIRECTION_OUT = 1; // 0x1
-  }
-
-  public static class IpSecTransform.Builder {
-    ctor public IpSecTransform.Builder(android.content.Context);
-    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
-    method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
-  }
-
   public class LinkAddress implements android.os.Parcelable {
     method public int describeContents();
     method public java.net.InetAddress getAddress();
@@ -24141,6 +24075,10 @@
     method public android.net.NetworkRequest.Builder removeCapability(int);
     method public android.net.NetworkRequest.Builder removeTransportType(int);
     method public android.net.NetworkRequest.Builder setNetworkSpecifier(java.lang.String);
+    method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
+  }
+
+  public abstract class NetworkSpecifier {
   }
 
   public class ParseException extends java.lang.RuntimeException {
@@ -25125,8 +25063,8 @@
   }
 
   public class DiscoverySession {
-    method public java.lang.String createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
-    method public java.lang.String createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
+    method public android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
+    method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
     method public void destroy();
     method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
   }
@@ -25212,8 +25150,8 @@
   }
 
   public class WifiAwareSession {
-    method public java.lang.String createNetworkSpecifierOpen(int, byte[]);
-    method public java.lang.String createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
+    method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, byte[]);
+    method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
     method public void destroy();
     method public void publish(android.net.wifi.aware.PublishConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
     method public void subscribe(android.net.wifi.aware.SubscribeConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
@@ -33654,6 +33592,16 @@
     field public static final java.lang.String SUBSCRIPTION_ID = "pending_sub_id";
   }
 
+  public static final class Telephony.ServiceStateTable {
+    method public static android.net.Uri getUriForSubscriptionId(int);
+    method public static android.net.Uri getUriForSubscriptionIdAndField(int, java.lang.String);
+    field public static final java.lang.String AUTHORITY = "service-state";
+    field public static final android.net.Uri CONTENT_URI;
+    field public static final java.lang.String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
+    field public static final java.lang.String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
+    field public static final java.lang.String VOICE_REG_STATE = "voice_reg_state";
+  }
+
   public static final class Telephony.Sms implements android.provider.BaseColumns android.provider.Telephony.TextBasedSmsColumns {
     method public static java.lang.String getDefaultSmsPackage(android.content.Context);
     field public static final android.net.Uri CONTENT_URI;
@@ -33835,6 +33783,8 @@
 
   public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
     method public static android.net.Uri buildSourceUri(java.lang.String);
+    field public static final java.lang.String ARCHIVED = "archived";
+    field public static final java.lang.String BACKED_UP = "backed_up";
     field public static final android.net.Uri CONTENT_URI;
     field public static final java.lang.String DATE = "date";
     field public static final java.lang.String DELETED = "deleted";
@@ -33842,6 +33792,7 @@
     field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
     field public static final java.lang.String DURATION = "duration";
     field public static final java.lang.String HAS_CONTENT = "has_content";
+    field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
     field public static final java.lang.String IS_READ = "is_read";
     field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
     field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -33849,6 +33800,7 @@
     field public static final java.lang.String NUMBER = "number";
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+    field public static final java.lang.String RESTORED = "restored";
     field public static final java.lang.String SOURCE_DATA = "source_data";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
     field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -37101,7 +37053,6 @@
   }
 
   public static final class Connection.RttModifyStatus {
-    ctor public Connection.RttModifyStatus();
     field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2
     field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3
     field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5
@@ -37459,6 +37410,7 @@
     method public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(java.lang.String);
     method public java.lang.String getLine1Number(android.telecom.PhoneAccountHandle);
     method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
+    method public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccountHandle getSimCallManager();
     method public java.lang.String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
     method public boolean handleMmi(java.lang.String);
@@ -37478,6 +37430,8 @@
     field public static final java.lang.String ACTION_CONFIGURE_PHONE_ACCOUNT = "android.telecom.action.CONFIGURE_PHONE_ACCOUNT";
     field public static final java.lang.String ACTION_DEFAULT_DIALER_CHANGED = "android.telecom.action.DEFAULT_DIALER_CHANGED";
     field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+    field public static final java.lang.String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED";
+    field public static final java.lang.String ACTION_PHONE_ACCOUNT_UNREGISTERED = "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED";
     field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
     field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
     field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
@@ -37577,10 +37531,12 @@
     field public static final java.lang.String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
     field public static final java.lang.String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
     field public static final java.lang.String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
+    field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
     field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
     field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
     field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
-    field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+    field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+    field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
     field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
     field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
     field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
@@ -37664,9 +37620,13 @@
     field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
     field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
     field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+    field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
     field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+    field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+    field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
     field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
     field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+    field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
     field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
     field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
   }
@@ -38164,9 +38124,8 @@
     method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
     method public int getCallState();
     method public android.os.PersistableBundle getCarrierConfig();
-    method public android.telephony.CellLocation getCellLocation();
+    method public deprecated android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
-    method public boolean getDataEnabled();
     method public int getDataNetworkType();
     method public int getDataState();
     method public deprecated java.lang.String getDeviceId();
@@ -38198,6 +38157,7 @@
     method public int getSimState();
     method public int getSimState(int);
     method public java.lang.String getSubscriberId();
+    method public java.lang.String getVisualVoicemailPackageName();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
     method public int getVoiceNetworkType();
@@ -38206,9 +38166,12 @@
     method public boolean hasIccCard();
     method public boolean iccCloseLogicalChannel(int);
     method public byte[] iccExchangeSimIO(int, int, int, int, int, java.lang.String);
-    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
+    method public deprecated android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
+    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String, int);
     method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+    method public boolean isConcurrentVoiceAndDataAllowed();
+    method public boolean isDataEnabled();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isNetworkRoaming();
     method public boolean isSmsCapable();
@@ -38218,6 +38181,7 @@
     method public boolean isWorldPhone();
     method public void listen(android.telephony.PhoneStateListener, int);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
+    method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
@@ -38292,6 +38256,57 @@
     field public static final java.lang.String VVM_TYPE_OMTP = "vvm_type_omtp";
   }
 
+  public static abstract class TelephonyManager.OnReceiveUssdResponseCallback {
+    ctor public TelephonyManager.OnReceiveUssdResponseCallback();
+    method public void onReceiveUssdResponse(java.lang.String, java.lang.CharSequence);
+    method public void onReceiveUssdResponseFailed(java.lang.String, int);
+  }
+
+  public abstract class VisualVoicemailService extends android.app.Service {
+    ctor public VisualVoicemailService();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract void onCellServiceConnected(android.telephony.VisualVoicemailService.VisualVoicemailTask, android.telecom.PhoneAccountHandle);
+    method public abstract void onSimRemoved(android.telephony.VisualVoicemailService.VisualVoicemailTask, android.telecom.PhoneAccountHandle);
+    method public abstract void onSmsReceived(android.telephony.VisualVoicemailService.VisualVoicemailTask, android.telephony.VisualVoicemailSms);
+    method public abstract void onStopped(android.telephony.VisualVoicemailService.VisualVoicemailTask);
+    method public static final void sendVisualVoicemailSms(android.content.Context, android.telecom.PhoneAccountHandle, java.lang.String, short, java.lang.String, android.app.PendingIntent);
+    method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.telephony.VisualVoicemailService";
+  }
+
+  public static class VisualVoicemailService.VisualVoicemailTask {
+    method public final void finish();
+  }
+
+  public final class VisualVoicemailSms implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.Bundle getFields();
+    method public java.lang.String getMessageBody();
+    method public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
+    method public java.lang.String getPrefix();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.VisualVoicemailSms> CREATOR;
+  }
+
+  public final class VisualVoicemailSmsFilterSettings implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.VisualVoicemailSmsFilterSettings> CREATOR;
+    field public static final int DESTINATION_PORT_ANY = -1; // 0xffffffff
+    field public static final int DESTINATION_PORT_DATA_SMS = -2; // 0xfffffffe
+    field public final java.lang.String clientPrefix;
+    field public final int destinationPort;
+    field public final java.util.List<java.lang.String> originatingNumbers;
+  }
+
+  public static class VisualVoicemailSmsFilterSettings.Builder {
+    ctor public VisualVoicemailSmsFilterSettings.Builder();
+    method public android.telephony.VisualVoicemailSmsFilterSettings build();
+    method public android.telephony.VisualVoicemailSmsFilterSettings.Builder setClientPrefix(java.lang.String);
+    method public android.telephony.VisualVoicemailSmsFilterSettings.Builder setDestinationPort(int);
+    method public android.telephony.VisualVoicemailSmsFilterSettings.Builder setOriginatingNumbers(java.util.List<java.lang.String>);
+  }
+
 }
 
 package android.telephony.cdma {
@@ -49695,15 +49710,15 @@
     ctor public DexClassLoader(java.lang.String, java.lang.String, java.lang.String, java.lang.ClassLoader);
   }
 
-  public final class DexFile {
-    ctor public DexFile(java.io.File) throws java.io.IOException;
-    ctor public DexFile(java.lang.String) throws java.io.IOException;
+  public final deprecated class DexFile {
+    ctor public deprecated DexFile(java.io.File) throws java.io.IOException;
+    ctor public deprecated DexFile(java.lang.String) throws java.io.IOException;
     method public void close() throws java.io.IOException;
     method public java.util.Enumeration<java.lang.String> entries();
     method public java.lang.String getName();
     method public static boolean isDexOptNeeded(java.lang.String) throws java.io.FileNotFoundException, java.io.IOException;
     method public java.lang.Class loadClass(java.lang.String, java.lang.ClassLoader);
-    method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
+    method public static deprecated dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
   }
 
   public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
@@ -52888,6 +52903,7 @@
     method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
     method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.lang.Class<?>...);
     method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
+    method public static java.lang.invoke.MethodHandle explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType);
     method public static java.lang.invoke.MethodHandle filterArguments(java.lang.invoke.MethodHandle, int, java.lang.invoke.MethodHandle...);
     method public static java.lang.invoke.MethodHandle filterReturnValue(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle foldArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
diff --git a/api/system-current.txt b/api/system-current.txt
index 12a0b0e..f33d506 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -55,6 +55,7 @@
     field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
     field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+    field public static final java.lang.String BIND_VISUAL_VOICEMAIL_SERVICE = "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE";
     field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION";
     field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
     field public static final java.lang.String BIND_VR_LISTENER_SERVICE = "android.permission.BIND_VR_LISTENER_SERVICE";
@@ -7380,6 +7381,7 @@
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int);
+    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int, android.os.Handler);
     method public boolean createBond();
     method public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
     method public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
@@ -7426,9 +7428,11 @@
     field public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; // 0x2
     field public static final int PAIRING_VARIANT_PIN = 0; // 0x0
     field public static final int PHY_LE_1M = 1; // 0x1
+    field public static final int PHY_LE_1M_MASK = 1; // 0x1
     field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_ANY = 7; // 0x7
-    field public static final int PHY_LE_CODED = 4; // 0x4
+    field public static final int PHY_LE_2M_MASK = 2; // 0x2
+    field public static final int PHY_LE_CODED = 3; // 0x3
+    field public static final int PHY_LE_CODED_MASK = 4; // 0x4
     field public static final int PHY_OPTION_NO_PREFERRED = 0; // 0x0
     field public static final int PHY_OPTION_S2 = 1; // 0x1
     field public static final int PHY_OPTION_S8 = 2; // 0x2
@@ -7804,7 +7808,7 @@
     method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
     method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
     method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
-    method public void setPeriodicAdvertisingEnable(boolean);
+    method public void setPeriodicAdvertisingEnabled(boolean);
     method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
     method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
   }
@@ -7841,14 +7845,11 @@
     method public boolean isScannable();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
-    field public static final int INTERVAL_HIGH = 160; // 0xa0
-    field public static final int INTERVAL_LOW = 1600; // 0x640
+    field public static final int INTERVAL_HIGH = 1600; // 0x640
+    field public static final int INTERVAL_LOW = 160; // 0xa0
     field public static final int INTERVAL_MAX = 16777215; // 0xffffff
     field public static final int INTERVAL_MEDIUM = 400; // 0x190
     field public static final int INTERVAL_MIN = 160; // 0xa0
-    field public static final int PHY_LE_1M = 1; // 0x1
-    field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_CODED = 3; // 0x3
     field public static final int TX_POWER_HIGH = 1; // 0x1
     field public static final int TX_POWER_LOW = -15; // 0xfffffff1
     field public static final int TX_POWER_MAX = 1; // 0x1
@@ -7894,7 +7895,6 @@
 
   public final class PeriodicAdvertisingParameters implements android.os.Parcelable {
     method public int describeContents();
-    method public boolean getEnable();
     method public boolean getIncludeTxPower();
     method public int getInterval();
     method public void writeToParcel(android.os.Parcel, int);
@@ -7904,7 +7904,6 @@
   public static final class PeriodicAdvertisingParameters.Builder {
     ctor public PeriodicAdvertisingParameters.Builder();
     method public android.bluetooth.le.PeriodicAdvertisingParameters build();
-    method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setEnable(boolean);
     method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setIncludeTxPower(boolean);
     method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setInterval(int);
   }
@@ -7992,11 +7991,10 @@
     field public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanResult> CREATOR;
     field public static final int DATA_COMPLETE = 0; // 0x0
     field public static final int DATA_TRUNCATED = 2; // 0x2
-    field public static final int PHY_LE_1M = 1; // 0x1
-    field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_CODED = 3; // 0x3
+    field public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0; // 0x0
     field public static final int PHY_UNUSED = 0; // 0x0
     field public static final int SID_NOT_PRESENT = 255; // 0xff
+    field public static final int TX_POWER_NOT_PRESENT = 127; // 0x7f
   }
 
   public final class ScanSettings implements android.os.Parcelable {
@@ -8017,9 +8015,7 @@
     field public static final int MATCH_NUM_FEW_ADVERTISEMENT = 2; // 0x2
     field public static final int MATCH_NUM_MAX_ADVERTISEMENT = 3; // 0x3
     field public static final int MATCH_NUM_ONE_ADVERTISEMENT = 1; // 0x1
-    field public static final int PHY_LE_1M = 1; // 0x1
     field public static final int PHY_LE_ALL_SUPPORTED = 255; // 0xff
-    field public static final int PHY_LE_CODED = 3; // 0x3
     field public static final int SCAN_MODE_BALANCED = 1; // 0x1
     field public static final int SCAN_MODE_LOW_LATENCY = 2; // 0x2
     field public static final int SCAN_MODE_LOW_POWER = 0; // 0x0
@@ -8661,7 +8657,6 @@
     field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
     field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
     field public static final java.lang.String INPUT_SERVICE = "input";
-    field public static final java.lang.String IPSEC_SERVICE = "ipsec";
     field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
     field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
     field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -19402,9 +19397,9 @@
     method protected final void computeGregorianFields(int);
     method protected int computeGregorianMonthStart(int, int);
     method protected int computeJulianDay();
-    method protected int computeMillisInDay();
+    method protected deprecated int computeMillisInDay();
     method protected void computeTime();
-    method protected int computeZoneOffset(long, int);
+    method protected deprecated int computeZoneOffset(long, int);
     method public int fieldDifference(java.util.Date, int);
     method protected java.lang.String fieldName(int);
     method protected static final long floorDivide(long, long);
@@ -25577,8 +25572,8 @@
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
-    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback);
-    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
+    method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, int);
+    method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler, int);
     method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
     method public deprecated boolean requestRouteToHost(int, int);
     method public deprecated void setNetworkPreference(int);
@@ -25716,69 +25711,6 @@
     field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
   }
 
-  public final class IpSecAlgorithm implements android.os.Parcelable {
-    ctor public IpSecAlgorithm(java.lang.String, byte[]);
-    ctor public IpSecAlgorithm(java.lang.String, byte[], int);
-    method public int describeContents();
-    method public byte[] getKey();
-    method public java.lang.String getName();
-    method public int getTruncationLengthBits();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final java.lang.String ALGO_AUTH_HMAC_MD5 = "hmac(md5)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)";
-    field public static final java.lang.String ALGO_CRYPT_AES_CBC = "cbc(aes)";
-    field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
-  }
-
-  public final class IpSecManager {
-    method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
-    method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
-    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
-    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
-    method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
-    method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
-    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
-  }
-
-  public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
-  }
-
-  public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
-    method public void close();
-    method public int getSpi();
-  }
-
-  public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
-    method public int getSpi();
-  }
-
-  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
-    method public void close();
-    method public int getPort();
-    method public java.io.FileDescriptor getSocket();
-  }
-
-  public final class IpSecTransform implements java.lang.AutoCloseable {
-    method public void close();
-    field public static final int DIRECTION_IN = 0; // 0x0
-    field public static final int DIRECTION_OUT = 1; // 0x1
-  }
-
-  public static class IpSecTransform.Builder {
-    ctor public IpSecTransform.Builder(android.content.Context);
-    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
-    method public android.net.IpSecTransform.Builder setNattKeepalive(int);
-    method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
-    method public android.net.IpSecTransform.Builder setUnderlyingNetwork(android.net.Network);
-  }
-
   public class LinkAddress implements android.os.Parcelable {
     method public int describeContents();
     method public java.net.InetAddress getAddress();
@@ -26004,6 +25936,7 @@
     method public android.net.NetworkRequest.Builder removeCapability(int);
     method public android.net.NetworkRequest.Builder removeTransportType(int);
     method public android.net.NetworkRequest.Builder setNetworkSpecifier(java.lang.String);
+    method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
   }
 
   public class NetworkScoreManager {
@@ -26023,6 +25956,9 @@
     field public static final java.lang.String EXTRA_PACKAGE_NAME = "packageName";
   }
 
+  public abstract class NetworkSpecifier {
+  }
+
   public class ParseException extends java.lang.RuntimeException {
     field public java.lang.String response;
   }
@@ -27681,9 +27617,9 @@
   }
 
   public class DiscoverySession {
-    method public java.lang.String createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
-    method public java.lang.String createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
-    method public java.lang.String createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]);
+    method public android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
+    method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
+    method public android.net.NetworkSpecifier createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]);
     method public void destroy();
     method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
   }
@@ -27769,9 +27705,9 @@
   }
 
   public class WifiAwareSession {
-    method public java.lang.String createNetworkSpecifierOpen(int, byte[]);
-    method public java.lang.String createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
-    method public java.lang.String createNetworkSpecifierPmk(int, byte[], byte[]);
+    method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, byte[]);
+    method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
+    method public android.net.NetworkSpecifier createNetworkSpecifierPmk(int, byte[], byte[]);
     method public void destroy();
     method public void publish(android.net.wifi.aware.PublishConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
     method public void subscribe(android.net.wifi.aware.SubscribeConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
@@ -36498,6 +36434,16 @@
     field public static final java.lang.String SUBSCRIPTION_ID = "pending_sub_id";
   }
 
+  public static final class Telephony.ServiceStateTable {
+    method public static android.net.Uri getUriForSubscriptionId(int);
+    method public static android.net.Uri getUriForSubscriptionIdAndField(int, java.lang.String);
+    field public static final java.lang.String AUTHORITY = "service-state";
+    field public static final android.net.Uri CONTENT_URI;
+    field public static final java.lang.String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
+    field public static final java.lang.String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
+    field public static final java.lang.String VOICE_REG_STATE = "voice_reg_state";
+  }
+
   public static final class Telephony.Sms implements android.provider.BaseColumns android.provider.Telephony.TextBasedSmsColumns {
     method public static java.lang.String getDefaultSmsPackage(android.content.Context);
     field public static final android.net.Uri CONTENT_URI;
@@ -36679,6 +36625,8 @@
 
   public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
     method public static android.net.Uri buildSourceUri(java.lang.String);
+    field public static final java.lang.String ARCHIVED = "archived";
+    field public static final java.lang.String BACKED_UP = "backed_up";
     field public static final android.net.Uri CONTENT_URI;
     field public static final java.lang.String DATE = "date";
     field public static final java.lang.String DELETED = "deleted";
@@ -36686,6 +36634,7 @@
     field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
     field public static final java.lang.String DURATION = "duration";
     field public static final java.lang.String HAS_CONTENT = "has_content";
+    field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
     field public static final java.lang.String IS_READ = "is_read";
     field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
     field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -36693,6 +36642,7 @@
     field public static final java.lang.String NUMBER = "number";
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+    field public static final java.lang.String RESTORED = "restored";
     field public static final java.lang.String SOURCE_DATA = "source_data";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
     field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -40086,7 +40036,6 @@
   }
 
   public static final class Connection.RttModifyStatus {
-    ctor public Connection.RttModifyStatus();
     field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2
     field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3
     field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5
@@ -40625,6 +40574,7 @@
     method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
     method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
     method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(java.lang.String);
+    method public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccountHandle getSimCallManager();
     method public java.lang.String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
     method public boolean handleMmi(java.lang.String);
@@ -40752,10 +40702,12 @@
     field public static final java.lang.String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
     field public static final java.lang.String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
     field public static final java.lang.String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
+    field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
     field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
     field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
     field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
-    field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+    field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+    field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
     field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
     field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
     field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
@@ -40839,9 +40791,13 @@
     field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
     field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
     field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+    field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
     field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+    field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+    field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
     field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
     field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+    field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
     field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
     field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
   }
@@ -41377,12 +41333,12 @@
     method public java.lang.String getCdmaMdn(int);
     method public java.lang.String getCdmaMin();
     method public java.lang.String getCdmaMin(int);
-    method public android.telephony.CellLocation getCellLocation();
+    method public deprecated android.telephony.CellLocation getCellLocation();
     method public int getCurrentPhoneType();
     method public int getCurrentPhoneType(int);
     method public int getDataActivity();
-    method public boolean getDataEnabled();
-    method public boolean getDataEnabled(int);
+    method public deprecated boolean getDataEnabled();
+    method public deprecated boolean getDataEnabled(int);
     method public int getDataNetworkType();
     method public int getDataState();
     method public deprecated java.lang.String getDeviceId();
@@ -41415,6 +41371,7 @@
     method public int getSimState(int);
     method public java.lang.String getSubscriberId();
     method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
+    method public java.lang.String getVisualVoicemailPackageName();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
     method public int getVoiceNetworkType();
@@ -41425,10 +41382,13 @@
     method public boolean hasIccCard();
     method public boolean iccCloseLogicalChannel(int);
     method public byte[] iccExchangeSimIO(int, int, int, int, int, java.lang.String);
-    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
+    method public deprecated android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
+    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String, int);
     method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+    method public boolean isConcurrentVoiceAndDataAllowed();
     method public boolean isDataConnectivityPossible();
+    method public boolean isDataEnabled();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isIdle();
     method public boolean isNetworkRoaming();
@@ -41445,6 +41405,7 @@
     method public void listen(android.telephony.PhoneStateListener, int);
     method public boolean needsOtaServiceProvisioning();
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
+    method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
     method public void setDataEnabled(boolean);
     method public void setDataEnabled(int, boolean);
@@ -41540,6 +41501,57 @@
     field public static final java.lang.String VVM_TYPE_OMTP = "vvm_type_omtp";
   }
 
+  public static abstract class TelephonyManager.OnReceiveUssdResponseCallback {
+    ctor public TelephonyManager.OnReceiveUssdResponseCallback();
+    method public void onReceiveUssdResponse(java.lang.String, java.lang.CharSequence);
+    method public void onReceiveUssdResponseFailed(java.lang.String, int);
+  }
+
+  public abstract class VisualVoicemailService extends android.app.Service {
+    ctor public VisualVoicemailService();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract void onCellServiceConnected(android.telephony.VisualVoicemailService.VisualVoicemailTask, android.telecom.PhoneAccountHandle);
+    method public abstract void onSimRemoved(android.telephony.VisualVoicemailService.VisualVoicemailTask, android.telecom.PhoneAccountHandle);
+    method public abstract void onSmsReceived(android.telephony.VisualVoicemailService.VisualVoicemailTask, android.telephony.VisualVoicemailSms);
+    method public abstract void onStopped(android.telephony.VisualVoicemailService.VisualVoicemailTask);
+    method public static final void sendVisualVoicemailSms(android.content.Context, android.telecom.PhoneAccountHandle, java.lang.String, short, java.lang.String, android.app.PendingIntent);
+    method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.telephony.VisualVoicemailService";
+  }
+
+  public static class VisualVoicemailService.VisualVoicemailTask {
+    method public final void finish();
+  }
+
+  public final class VisualVoicemailSms implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.Bundle getFields();
+    method public java.lang.String getMessageBody();
+    method public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
+    method public java.lang.String getPrefix();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.VisualVoicemailSms> CREATOR;
+  }
+
+  public final class VisualVoicemailSmsFilterSettings implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.VisualVoicemailSmsFilterSettings> CREATOR;
+    field public static final int DESTINATION_PORT_ANY = -1; // 0xffffffff
+    field public static final int DESTINATION_PORT_DATA_SMS = -2; // 0xfffffffe
+    field public final java.lang.String clientPrefix;
+    field public final int destinationPort;
+    field public final java.util.List<java.lang.String> originatingNumbers;
+  }
+
+  public static class VisualVoicemailSmsFilterSettings.Builder {
+    ctor public VisualVoicemailSmsFilterSettings.Builder();
+    method public android.telephony.VisualVoicemailSmsFilterSettings build();
+    method public android.telephony.VisualVoicemailSmsFilterSettings.Builder setClientPrefix(java.lang.String);
+    method public android.telephony.VisualVoicemailSmsFilterSettings.Builder setDestinationPort(int);
+    method public android.telephony.VisualVoicemailSmsFilterSettings.Builder setOriginatingNumbers(java.util.List<java.lang.String>);
+  }
+
 }
 
 package android.telephony.cdma {
@@ -53321,15 +53333,15 @@
     ctor public DexClassLoader(java.lang.String, java.lang.String, java.lang.String, java.lang.ClassLoader);
   }
 
-  public final class DexFile {
-    ctor public DexFile(java.io.File) throws java.io.IOException;
-    ctor public DexFile(java.lang.String) throws java.io.IOException;
+  public final deprecated class DexFile {
+    ctor public deprecated DexFile(java.io.File) throws java.io.IOException;
+    ctor public deprecated DexFile(java.lang.String) throws java.io.IOException;
     method public void close() throws java.io.IOException;
     method public java.util.Enumeration<java.lang.String> entries();
     method public java.lang.String getName();
     method public static boolean isDexOptNeeded(java.lang.String) throws java.io.FileNotFoundException, java.io.IOException;
     method public java.lang.Class loadClass(java.lang.String, java.lang.ClassLoader);
-    method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
+    method public static deprecated dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
   }
 
   public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
@@ -56514,6 +56526,7 @@
     method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
     method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.lang.Class<?>...);
     method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
+    method public static java.lang.invoke.MethodHandle explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType);
     method public static java.lang.invoke.MethodHandle filterArguments(java.lang.invoke.MethodHandle, int, java.lang.invoke.MethodHandle...);
     method public static java.lang.invoke.MethodHandle filterReturnValue(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle foldArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
diff --git a/api/test-current.txt b/api/test-current.txt
index a0362b4..bd96efb 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -36,6 +36,7 @@
     field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
     field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
+    field public static final java.lang.String BIND_VISUAL_VOICEMAIL_SERVICE = "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE";
     field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION";
     field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
     field public static final java.lang.String BIND_VR_LISTENER_SERVICE = "android.permission.BIND_VR_LISTENER_SERVICE";
@@ -7086,6 +7087,7 @@
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int);
+    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int, android.os.Handler);
     method public boolean createBond();
     method public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
     method public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
@@ -7130,9 +7132,11 @@
     field public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; // 0x2
     field public static final int PAIRING_VARIANT_PIN = 0; // 0x0
     field public static final int PHY_LE_1M = 1; // 0x1
+    field public static final int PHY_LE_1M_MASK = 1; // 0x1
     field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_ANY = 7; // 0x7
-    field public static final int PHY_LE_CODED = 4; // 0x4
+    field public static final int PHY_LE_2M_MASK = 2; // 0x2
+    field public static final int PHY_LE_CODED = 3; // 0x3
+    field public static final int PHY_LE_CODED_MASK = 4; // 0x4
     field public static final int PHY_OPTION_NO_PREFERRED = 0; // 0x0
     field public static final int PHY_OPTION_S2 = 1; // 0x1
     field public static final int PHY_OPTION_S8 = 2; // 0x2
@@ -7508,7 +7512,7 @@
     method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
     method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
     method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
-    method public void setPeriodicAdvertisingEnable(boolean);
+    method public void setPeriodicAdvertisingEnabled(boolean);
     method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
     method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
   }
@@ -7545,14 +7549,11 @@
     method public boolean isScannable();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertisingSetParameters> CREATOR;
-    field public static final int INTERVAL_HIGH = 160; // 0xa0
-    field public static final int INTERVAL_LOW = 1600; // 0x640
+    field public static final int INTERVAL_HIGH = 1600; // 0x640
+    field public static final int INTERVAL_LOW = 160; // 0xa0
     field public static final int INTERVAL_MAX = 16777215; // 0xffffff
     field public static final int INTERVAL_MEDIUM = 400; // 0x190
     field public static final int INTERVAL_MIN = 160; // 0xa0
-    field public static final int PHY_LE_1M = 1; // 0x1
-    field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_CODED = 3; // 0x3
     field public static final int TX_POWER_HIGH = 1; // 0x1
     field public static final int TX_POWER_LOW = -15; // 0xfffffff1
     field public static final int TX_POWER_MAX = 1; // 0x1
@@ -7595,7 +7596,6 @@
 
   public final class PeriodicAdvertisingParameters implements android.os.Parcelable {
     method public int describeContents();
-    method public boolean getEnable();
     method public boolean getIncludeTxPower();
     method public int getInterval();
     method public void writeToParcel(android.os.Parcel, int);
@@ -7605,7 +7605,6 @@
   public static final class PeriodicAdvertisingParameters.Builder {
     ctor public PeriodicAdvertisingParameters.Builder();
     method public android.bluetooth.le.PeriodicAdvertisingParameters build();
-    method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setEnable(boolean);
     method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setIncludeTxPower(boolean);
     method public android.bluetooth.le.PeriodicAdvertisingParameters.Builder setInterval(int);
   }
@@ -7683,11 +7682,10 @@
     field public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanResult> CREATOR;
     field public static final int DATA_COMPLETE = 0; // 0x0
     field public static final int DATA_TRUNCATED = 2; // 0x2
-    field public static final int PHY_LE_1M = 1; // 0x1
-    field public static final int PHY_LE_2M = 2; // 0x2
-    field public static final int PHY_LE_CODED = 3; // 0x3
+    field public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0; // 0x0
     field public static final int PHY_UNUSED = 0; // 0x0
     field public static final int SID_NOT_PRESENT = 255; // 0xff
+    field public static final int TX_POWER_NOT_PRESENT = 127; // 0x7f
   }
 
   public final class ScanSettings implements android.os.Parcelable {
@@ -7708,9 +7706,7 @@
     field public static final int MATCH_NUM_FEW_ADVERTISEMENT = 2; // 0x2
     field public static final int MATCH_NUM_MAX_ADVERTISEMENT = 3; // 0x3
     field public static final int MATCH_NUM_ONE_ADVERTISEMENT = 1; // 0x1
-    field public static final int PHY_LE_1M = 1; // 0x1
     field public static final int PHY_LE_ALL_SUPPORTED = 255; // 0xff
-    field public static final int PHY_LE_CODED = 3; // 0x3
     field public static final int SCAN_MODE_BALANCED = 1; // 0x1
     field public static final int SCAN_MODE_LOW_LATENCY = 2; // 0x2
     field public static final int SCAN_MODE_LOW_POWER = 0; // 0x0
@@ -8337,7 +8333,6 @@
     field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
     field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
     field public static final java.lang.String INPUT_SERVICE = "input";
-    field public static final java.lang.String IPSEC_SERVICE = "ipsec";
     field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
     field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
     field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -18205,9 +18200,9 @@
     method protected final void computeGregorianFields(int);
     method protected int computeGregorianMonthStart(int, int);
     method protected int computeJulianDay();
-    method protected int computeMillisInDay();
+    method protected deprecated int computeMillisInDay();
     method protected void computeTime();
-    method protected int computeZoneOffset(long, int);
+    method protected deprecated int computeZoneOffset(long, int);
     method public int fieldDifference(java.util.Date, int);
     method protected java.lang.String fieldName(int);
     method protected static final long floorDivide(long, long);
@@ -23863,8 +23858,8 @@
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
-    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback);
-    method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
+    method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, int);
+    method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler, int);
     method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
     method public deprecated boolean requestRouteToHost(int, int);
     method public deprecated void setNetworkPreference(int);
@@ -23952,67 +23947,6 @@
     field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
   }
 
-  public final class IpSecAlgorithm implements android.os.Parcelable {
-    ctor public IpSecAlgorithm(java.lang.String, byte[]);
-    ctor public IpSecAlgorithm(java.lang.String, byte[], int);
-    method public int describeContents();
-    method public byte[] getKey();
-    method public java.lang.String getName();
-    method public int getTruncationLengthBits();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final java.lang.String ALGO_AUTH_HMAC_MD5 = "hmac(md5)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)";
-    field public static final java.lang.String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)";
-    field public static final java.lang.String ALGO_CRYPT_AES_CBC = "cbc(aes)";
-    field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
-  }
-
-  public final class IpSecManager {
-    method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
-    method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
-    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
-    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
-    method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
-    method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
-    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
-  }
-
-  public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
-  }
-
-  public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
-    method public void close();
-    method public int getSpi();
-  }
-
-  public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
-    method public int getSpi();
-  }
-
-  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
-    method public void close();
-    method public int getPort();
-    method public java.io.FileDescriptor getSocket();
-  }
-
-  public final class IpSecTransform implements java.lang.AutoCloseable {
-    method public void close();
-    field public static final int DIRECTION_IN = 0; // 0x0
-    field public static final int DIRECTION_OUT = 1; // 0x1
-  }
-
-  public static class IpSecTransform.Builder {
-    ctor public IpSecTransform.Builder(android.content.Context);
-    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
-    method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
-    method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
-  }
-
   public class LinkAddress implements android.os.Parcelable {
     method public int describeContents();
     method public java.net.InetAddress getAddress();
@@ -24215,6 +24149,10 @@
     method public android.net.NetworkRequest.Builder removeCapability(int);
     method public android.net.NetworkRequest.Builder removeTransportType(int);
     method public android.net.NetworkRequest.Builder setNetworkSpecifier(java.lang.String);
+    method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
+  }
+
+  public abstract class NetworkSpecifier {
   }
 
   public class ParseException extends java.lang.RuntimeException {
@@ -25199,8 +25137,8 @@
   }
 
   public class DiscoverySession {
-    method public java.lang.String createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
-    method public java.lang.String createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
+    method public android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
+    method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
     method public void destroy();
     method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
   }
@@ -25286,8 +25224,8 @@
   }
 
   public class WifiAwareSession {
-    method public java.lang.String createNetworkSpecifierOpen(int, byte[]);
-    method public java.lang.String createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
+    method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, byte[]);
+    method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, byte[], java.lang.String);
     method public void destroy();
     method public void publish(android.net.wifi.aware.PublishConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
     method public void subscribe(android.net.wifi.aware.SubscribeConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler);
@@ -33735,6 +33673,16 @@
     field public static final java.lang.String SUBSCRIPTION_ID = "pending_sub_id";
   }
 
+  public static final class Telephony.ServiceStateTable {
+    method public static android.net.Uri getUriForSubscriptionId(int);
+    method public static android.net.Uri getUriForSubscriptionIdAndField(int, java.lang.String);
+    field public static final java.lang.String AUTHORITY = "service-state";
+    field public static final android.net.Uri CONTENT_URI;
+    field public static final java.lang.String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
+    field public static final java.lang.String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
+    field public static final java.lang.String VOICE_REG_STATE = "voice_reg_state";
+  }
+
   public static final class Telephony.Sms implements android.provider.BaseColumns android.provider.Telephony.TextBasedSmsColumns {
     method public static java.lang.String getDefaultSmsPackage(android.content.Context);
     field public static final android.net.Uri CONTENT_URI;
@@ -33917,6 +33865,8 @@
 
   public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
     method public static android.net.Uri buildSourceUri(java.lang.String);
+    field public static final java.lang.String ARCHIVED = "archived";
+    field public static final java.lang.String BACKED_UP = "backed_up";
     field public static final android.net.Uri CONTENT_URI;
     field public static final java.lang.String DATE = "date";
     field public static final java.lang.String DELETED = "deleted";
@@ -33924,6 +33874,7 @@
     field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
     field public static final java.lang.String DURATION = "duration";
     field public static final java.lang.String HAS_CONTENT = "has_content";
+    field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
     field public static final java.lang.String IS_READ = "is_read";
     field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
     field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -33931,6 +33882,7 @@
     field public static final java.lang.String NUMBER = "number";
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+    field public static final java.lang.String RESTORED = "restored";
     field public static final java.lang.String SOURCE_DATA = "source_data";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
     field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -37192,7 +37144,6 @@
   }
 
   public static final class Connection.RttModifyStatus {
-    ctor public Connection.RttModifyStatus();
     field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2
     field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3
     field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5
@@ -37558,6 +37509,7 @@
     method public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(java.lang.String);
     method public java.lang.String getLine1Number(android.telecom.PhoneAccountHandle);
     method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
+    method public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccountHandle getSimCallManager();
     method public java.lang.String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
     method public boolean handleMmi(java.lang.String);
@@ -37577,6 +37529,8 @@
     field public static final java.lang.String ACTION_CONFIGURE_PHONE_ACCOUNT = "android.telecom.action.CONFIGURE_PHONE_ACCOUNT";
     field public static final java.lang.String ACTION_DEFAULT_DIALER_CHANGED = "android.telecom.action.DEFAULT_DIALER_CHANGED";
     field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+    field public static final java.lang.String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED";
+    field public static final java.lang.String ACTION_PHONE_ACCOUNT_UNREGISTERED = "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED";
     field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
     field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
     field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
@@ -37676,10 +37630,12 @@
     field public static final java.lang.String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
     field public static final java.lang.String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
     field public static final java.lang.String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
+    field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
     field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
     field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
     field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
-    field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+    field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+    field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
     field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
     field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
     field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
@@ -37763,9 +37719,13 @@
     field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
     field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
     field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+    field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
     field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+    field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+    field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
     field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
     field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+    field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
     field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
     field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
   }
@@ -38263,9 +38223,8 @@
     method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
     method public int getCallState();
     method public android.os.PersistableBundle getCarrierConfig();
-    method public android.telephony.CellLocation getCellLocation();
+    method public deprecated android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
-    method public boolean getDataEnabled();
     method public int getDataNetworkType();
     method public int getDataState();
     method public deprecated java.lang.String getDeviceId();
@@ -38297,6 +38256,7 @@
     method public int getSimState();
     method public int getSimState(int);
     method public java.lang.String getSubscriberId();
+    method public java.lang.String getVisualVoicemailPackageName();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
     method public int getVoiceNetworkType();
@@ -38305,9 +38265,12 @@
     method public boolean hasIccCard();
     method public boolean iccCloseLogicalChannel(int);
     method public byte[] iccExchangeSimIO(int, int, int, int, int, java.lang.String);
-    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
+    method public deprecated android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
+    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String, int);
     method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+    method public boolean isConcurrentVoiceAndDataAllowed();
+    method public boolean isDataEnabled();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isNetworkRoaming();
     method public boolean isSmsCapable();
@@ -38317,6 +38280,7 @@
     method public boolean isWorldPhone();
     method public void listen(android.telephony.PhoneStateListener, int);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
+    method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
@@ -38391,6 +38355,57 @@
     field public static final java.lang.String VVM_TYPE_OMTP = "vvm_type_omtp";
   }
 
+  public static abstract class TelephonyManager.OnReceiveUssdResponseCallback {
+    ctor public TelephonyManager.OnReceiveUssdResponseCallback();
+    method public void onReceiveUssdResponse(java.lang.String, java.lang.CharSequence);
+    method public void onReceiveUssdResponseFailed(java.lang.String, int);
+  }
+
+  public abstract class VisualVoicemailService extends android.app.Service {
+    ctor public VisualVoicemailService();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract void onCellServiceConnected(android.telephony.VisualVoicemailService.VisualVoicemailTask, android.telecom.PhoneAccountHandle);
+    method public abstract void onSimRemoved(android.telephony.VisualVoicemailService.VisualVoicemailTask, android.telecom.PhoneAccountHandle);
+    method public abstract void onSmsReceived(android.telephony.VisualVoicemailService.VisualVoicemailTask, android.telephony.VisualVoicemailSms);
+    method public abstract void onStopped(android.telephony.VisualVoicemailService.VisualVoicemailTask);
+    method public static final void sendVisualVoicemailSms(android.content.Context, android.telecom.PhoneAccountHandle, java.lang.String, short, java.lang.String, android.app.PendingIntent);
+    method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.telephony.VisualVoicemailService";
+  }
+
+  public static class VisualVoicemailService.VisualVoicemailTask {
+    method public final void finish();
+  }
+
+  public final class VisualVoicemailSms implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.os.Bundle getFields();
+    method public java.lang.String getMessageBody();
+    method public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
+    method public java.lang.String getPrefix();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.VisualVoicemailSms> CREATOR;
+  }
+
+  public final class VisualVoicemailSmsFilterSettings implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.VisualVoicemailSmsFilterSettings> CREATOR;
+    field public static final int DESTINATION_PORT_ANY = -1; // 0xffffffff
+    field public static final int DESTINATION_PORT_DATA_SMS = -2; // 0xfffffffe
+    field public final java.lang.String clientPrefix;
+    field public final int destinationPort;
+    field public final java.util.List<java.lang.String> originatingNumbers;
+  }
+
+  public static class VisualVoicemailSmsFilterSettings.Builder {
+    ctor public VisualVoicemailSmsFilterSettings.Builder();
+    method public android.telephony.VisualVoicemailSmsFilterSettings build();
+    method public android.telephony.VisualVoicemailSmsFilterSettings.Builder setClientPrefix(java.lang.String);
+    method public android.telephony.VisualVoicemailSmsFilterSettings.Builder setDestinationPort(int);
+    method public android.telephony.VisualVoicemailSmsFilterSettings.Builder setOriginatingNumbers(java.util.List<java.lang.String>);
+  }
+
 }
 
 package android.telephony.cdma {
@@ -49804,15 +49819,15 @@
     ctor public DexClassLoader(java.lang.String, java.lang.String, java.lang.String, java.lang.ClassLoader);
   }
 
-  public final class DexFile {
-    ctor public DexFile(java.io.File) throws java.io.IOException;
-    ctor public DexFile(java.lang.String) throws java.io.IOException;
+  public final deprecated class DexFile {
+    ctor public deprecated DexFile(java.io.File) throws java.io.IOException;
+    ctor public deprecated DexFile(java.lang.String) throws java.io.IOException;
     method public void close() throws java.io.IOException;
     method public java.util.Enumeration<java.lang.String> entries();
     method public java.lang.String getName();
     method public static boolean isDexOptNeeded(java.lang.String) throws java.io.FileNotFoundException, java.io.IOException;
     method public java.lang.Class loadClass(java.lang.String, java.lang.ClassLoader);
-    method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
+    method public static deprecated dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
   }
 
   public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
@@ -52997,6 +53012,7 @@
     method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
     method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.lang.Class<?>...);
     method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
+    method public static java.lang.invoke.MethodHandle explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType);
     method public static java.lang.invoke.MethodHandle filterArguments(java.lang.invoke.MethodHandle, int, java.lang.invoke.MethodHandle...);
     method public static java.lang.invoke.MethodHandle filterReturnValue(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle foldArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
diff --git a/cmds/uiautomator/instrumentation/Android.mk b/cmds/uiautomator/instrumentation/Android.mk
index 0c93b4c..e6cbdb4 100644
--- a/cmds/uiautomator/instrumentation/Android.mk
+++ b/cmds/uiautomator/instrumentation/Android.mk
@@ -22,6 +22,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, testrunner-src) \
     $(call all-java-files-under, ../library/core-src)
 LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
 LOCAL_MODULE := uiautomator-instrumentation
 # TODO: change this to 18 when it's available
 LOCAL_SDK_VERSION := current
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ce019cac..1e5ea26 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -26,6 +26,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.job.IJobScheduler;
 import android.app.job.JobScheduler;
+import android.app.timezone.RulesManager;
 import android.app.trust.TrustManager;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.NetworkStatsManager;
@@ -786,6 +787,13 @@
                 return new ContextHubManager(ctx.getOuterContext(),
                   ctx.mMainThread.getHandler().getLooper());
             }});
+
+        registerService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, RulesManager.class,
+                new CachedServiceFetcher<RulesManager>() {
+            @Override
+            public RulesManager createService(ContextImpl ctx) {
+                return new RulesManager(ctx.getOuterContext());
+            }});
     }
 
     /**
diff --git a/core/java/android/app/timezone/Callback.java b/core/java/android/app/timezone/Callback.java
new file mode 100644
index 0000000..b51e5ba
--- /dev/null
+++ b/core/java/android/app/timezone/Callback.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Callback interface for receiving information about an async time zone operation.
+ * The methods will be called on your application's main thread.
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public abstract class Callback {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_INSTALL_BAD_DISTRO_STRUCTURE,
+        ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION, ERROR_INSTALL_RULES_TOO_OLD,
+        ERROR_INSTALL_VALIDATION_ERROR})
+    public @interface AsyncResultCode {}
+
+    /**
+     * Indicates that an operation succeeded.
+     */
+    public static final int SUCCESS = 0;
+
+    /**
+     * Indicates an install / uninstall did not fully succeed for an unknown reason.
+     */
+    public static final int ERROR_UNKNOWN_FAILURE = 1;
+
+    /**
+     * Indicates an install failed because of a structural issue with the provided distro,
+     * e.g. it wasn't in the right format or the contents were structured incorrectly.
+     */
+    public static final int ERROR_INSTALL_BAD_DISTRO_STRUCTURE = 2;
+
+    /**
+     * Indicates an install failed because of a versioning issue with the provided distro,
+     * e.g. it was created for a different version of Android.
+     */
+    public static final int ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION = 3;
+
+    /**
+     * Indicates an install failed because the rules provided are too old for the device,
+     * e.g. the Android device shipped with a newer rules version.
+     */
+    public static final int ERROR_INSTALL_RULES_TOO_OLD = 4;
+
+    /**
+     * Indicates an install failed because the distro contents failed validation.
+     */
+    public static final int ERROR_INSTALL_VALIDATION_ERROR = 5;
+
+    /**
+     * Reports the result of an async time zone operation.
+     */
+    public abstract void onFinished(@AsyncResultCode int status);
+}
diff --git a/core/java/android/app/timezone/DistroFormatVersion.java b/core/java/android/app/timezone/DistroFormatVersion.java
new file mode 100644
index 0000000..e879e8f
--- /dev/null
+++ b/core/java/android/app/timezone/DistroFormatVersion.java
@@ -0,0 +1,120 @@
+/*
+ * 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.app.timezone;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Versioning information about a distro's format or a device's supported format.
+ *
+ * <p>The following properties are included:
+ * <dl>
+ *     <dt>majorVersion</dt>
+ *     <dd>the major distro format version. Major versions differences are not compatible - e.g.
+ *     2 is not compatible with 1 or 3.</dd>
+ *     <dt>minorVersion</dt>
+ *     <dd>the minor distro format version. Minor versions should be backwards compatible iff the
+ *     major versions match exactly, i.e. version 2.2 will be compatible with 2.1 devices but not
+ *     2.3 devices.</dd>
+ * </dl>
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class DistroFormatVersion implements Parcelable {
+
+    private final int mMajorVersion;
+    private final int mMinorVersion;
+
+    public DistroFormatVersion(int majorVersion, int minorVersion) {
+        mMajorVersion = Utils.validateVersion("major", majorVersion);
+        mMinorVersion = Utils.validateVersion("minor", minorVersion);
+    }
+
+    public static final Creator<DistroFormatVersion> CREATOR = new Creator<DistroFormatVersion>() {
+        public DistroFormatVersion createFromParcel(Parcel in) {
+            int majorVersion = in.readInt();
+            int minorVersion = in.readInt();
+            return new DistroFormatVersion(majorVersion, minorVersion);
+        }
+
+        public DistroFormatVersion[] newArray(int size) {
+            return new DistroFormatVersion[size];
+        }
+    };
+
+    public int getMajorVersion() {
+        return mMajorVersion;
+    }
+
+    public int getMinorVersion() {
+        return mMinorVersion;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mMajorVersion);
+        out.writeInt(mMinorVersion);
+    }
+
+    /**
+     * If this object describes a device's supported version and the parameter describes a distro's
+     * version, this method returns whether the device would accept the distro.
+     */
+    public boolean supports(DistroFormatVersion distroFormatVersion) {
+        return mMajorVersion == distroFormatVersion.mMajorVersion
+                && mMinorVersion <= distroFormatVersion.mMinorVersion;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DistroFormatVersion that = (DistroFormatVersion) o;
+
+        if (mMajorVersion != that.mMajorVersion) {
+            return false;
+        }
+        return mMinorVersion == that.mMinorVersion;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mMajorVersion;
+        result = 31 * result + mMinorVersion;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "DistroFormatVersion{"
+                + "mMajorVersion=" + mMajorVersion
+                + ", mMinorVersion=" + mMinorVersion
+                + '}';
+    }
+}
diff --git a/core/java/android/app/timezone/DistroRulesVersion.java b/core/java/android/app/timezone/DistroRulesVersion.java
new file mode 100644
index 0000000..5503ce1
--- /dev/null
+++ b/core/java/android/app/timezone/DistroRulesVersion.java
@@ -0,0 +1,128 @@
+/*
+ * 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.app.timezone;
+
+import static android.app.timezone.Utils.validateRulesVersion;
+import static android.app.timezone.Utils.validateVersion;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Versioning information about a set of time zone rules.
+ *
+ * <p>The following properties are included:
+ * <dl>
+ *     <dt>rulesVersion</dt>
+ *     <dd>the IANA rules version. e.g. "2017a"</dd>
+ *     <dt>revision</dt>
+ *     <dd>the revision for the rules. Allows there to be several revisions for a given IANA rules
+ *     release. Numerically higher is newer.</dd>
+ * </dl>
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class DistroRulesVersion implements Parcelable {
+
+    private final String mRulesVersion;
+    private final int mRevision;
+
+    public DistroRulesVersion(String rulesVersion, int revision) {
+        mRulesVersion = validateRulesVersion("rulesVersion", rulesVersion);
+        mRevision = validateVersion("revision", revision);
+    }
+
+    public static final Creator<DistroRulesVersion> CREATOR = new Creator<DistroRulesVersion>() {
+        public DistroRulesVersion createFromParcel(Parcel in) {
+            String rulesVersion = in.readString();
+            int revision = in.readInt();
+            return new DistroRulesVersion(rulesVersion, revision);
+        }
+
+        public DistroRulesVersion[] newArray(int size) {
+            return new DistroRulesVersion[size];
+        }
+    };
+
+    public String getRulesVersion() {
+        return mRulesVersion;
+    }
+
+    public int getRevision() {
+        return mRevision;
+    }
+
+    /**
+     * Returns true if this DistroRulesVersion is older than the one supplied. It returns false if
+     * it is the same or newer. This method compares the {@code rulesVersion} and the
+     * {@code revision}.
+     */
+    public boolean isOlderThan(DistroRulesVersion distroRulesVersion) {
+        int rulesComparison = mRulesVersion.compareTo(distroRulesVersion.mRulesVersion);
+        if (rulesComparison < 0) {
+            return true;
+        }
+        if (rulesComparison > 0) {
+            return false;
+        }
+        return mRevision < distroRulesVersion.mRevision;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mRulesVersion);
+        out.writeInt(mRevision);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DistroRulesVersion that = (DistroRulesVersion) o;
+
+        if (mRevision != that.mRevision) {
+            return false;
+        }
+        return mRulesVersion.equals(that.mRulesVersion);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mRulesVersion.hashCode();
+        result = 31 * result + mRevision;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "DistroRulesVersion{"
+                + "mRulesVersion='" + mRulesVersion + '\''
+                + ", mRevision='" + mRevision + '\''
+                + '}';
+    }
+}
diff --git a/core/java/android/app/timezone/ICallback.aidl b/core/java/android/app/timezone/ICallback.aidl
new file mode 100644
index 0000000..519ef1a
--- /dev/null
+++ b/core/java/android/app/timezone/ICallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.app.timezone;
+
+/**
+ * Callback interface for a timezone updater to receive information about the success or failure of
+ * an installation/uninstallation attempt.
+ *
+ * {@hide}
+ */
+oneway interface ICallback {
+    void onFinished(int error);
+}
\ No newline at end of file
diff --git a/core/java/android/app/timezone/IRulesManager.aidl b/core/java/android/app/timezone/IRulesManager.aidl
new file mode 100644
index 0000000..40f3fd2
--- /dev/null
+++ b/core/java/android/app/timezone/IRulesManager.aidl
@@ -0,0 +1,84 @@
+/*
+ * 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.app.timezone;
+
+import android.app.timezone.ICallback;
+import android.app.timezone.RulesState;
+import android.os.ParcelFileDescriptor;
+
+ /**
+  * Interface to the TimeZone Rules Manager Service.
+  *
+  * <p>This interface is only intended for system apps to call. They should use the
+  * {@link android.app.timezone.RulesManager} class rather than going through this
+  * Binder interface directly. See {@link android.app.timezone.RulesManager} for more complete
+  * documentation.
+  *
+  * {@hide}
+  */
+interface IRulesManager {
+
+    /**
+     * Returns information about the current time zone rules state such as the IANA version of
+     * the system and any currently installed distro. This method is intended to allow clients to
+     * determine if the current state can be improved; for example by passing the information to a
+     * server that may provide a new distro for download.
+     */
+    RulesState getRulesState();
+
+    /**
+     * Requests installation of the supplied distro. The distro must have been checked for integrity
+     * by the caller or have been received via a trusted mechanism.
+     *
+     * @param distroFileDescriptor the file descriptor for the distro
+     * @param checkToken an optional token provided if the install was triggered in response to a
+     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+     * @param callback the {@link ICallback} to receive callbacks related to the
+     *     installation
+     * @return zero if the installation will be attempted; nonzero on error
+     */
+    int requestInstall(in ParcelFileDescriptor distroFileDescriptor, in byte[] checkToken,
+            ICallback callback);
+
+    /**
+     * Requests uninstallation of the currently installed distro (leaving the device with no
+     * distro installed).
+     *
+     * @param checkToken an optional token provided if the uninstall was triggered in response to a
+     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+     * @param callback the {@link ICallback} to receive callbacks related to the
+     *     uninstall
+     * @return zero if the uninstallation will be attempted; nonzero on error
+     */
+    int requestUninstall(in byte[] checkToken, ICallback callback);
+
+    /**
+     * Requests the system does not modify the currently installed time zone distro, if any. This
+     * method records the fact that a time zone check operation triggered by the system is now
+     * complete and there was nothing to do. The token passed should be the one presented when the
+     * check was triggered.
+     *
+     * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
+     * should be careful not to pass false if the failure is unlikely to resolve by itself.
+     *
+     * @param checkToken an optional token provided if the install was triggered in response to a
+     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+     * @param success true if the check was successful, false if it was not successful but may
+     *     succeed if it is retried
+     */
+    void requestNothing(in byte[] token, boolean success);
+}
diff --git a/core/java/android/app/timezone/RulesManager.java b/core/java/android/app/timezone/RulesManager.java
new file mode 100644
index 0000000..649d894
--- /dev/null
+++ b/core/java/android/app/timezone/RulesManager.java
@@ -0,0 +1,212 @@
+/*
+ * 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.app.timezone;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * The interface through which a time zone update application interacts with the Android system
+ * to handle time zone rule updates.
+ *
+ * <p>This interface is intended for use with the default APK-based time zone rules update
+ * application but it can also be used by OEMs if that mechanism is turned off using configuration.
+ * All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
+ * permission.
+ *
+ * <p>When using the default mechanism, when properly configured the Android system will send a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
+ * {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} extra to the time zone rules updater application
+ * when it detects that it or the OEM's APK containing time zone rules data has been modified. The
+ * updater application is then responsible for calling one of
+ * {@link #requestInstall(ParcelFileDescriptor, byte[], Callback)},
+ * {@link #requestUninstall(byte[], Callback)} or
+ * {@link #requestNothing(byte[], boolean)}, indicating, respectively, whether a new time zone rules
+ * distro should be installed, the current distro should be uninstalled, or there is nothing to do
+ * (or that the correct operation could not be determined due to an error). In each case the updater
+ * must pass the {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} value it received from the intent
+ * back so the system in the {@code checkToken} parameter.
+ *
+ * <p>If OEMs want to handle their own time zone rules updates, perhaps via a server-side component
+ * rather than an APK, then they should disable the default triggering mechanism in config and are
+ * responsible for triggering their own update checks / installs / uninstalls. In this case the
+ * "check token" parameter can be left null and there is never any need to call
+ * {@link #requestNothing(byte[], boolean)}.
+ *
+ * <p>OEMs should not mix the default mechanism and their own as this could lead to conflicts and
+ * unnecessary checks being triggered.
+ *
+ * <p>Applications obtain this using {@link android.app.Activity#getSystemService(String)} with
+ * {@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;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_OPERATION_IN_PROGRESS})
+    public @interface ResultCode {}
+
+    /**
+     * Indicates that an operation succeeded.
+     */
+    public static final int SUCCESS = 0;
+
+    /**
+     * Indicates that an install/uninstall cannot be initiated because there is one already in
+     * progress.
+     */
+    public static final int ERROR_OPERATION_IN_PROGRESS = 1;
+
+    /**
+     * Indicates an install / uninstall did not fully succeed for an unknown reason.
+     */
+    public static final int ERROR_UNKNOWN_FAILURE = 2;
+
+    private final Context mContext;
+    private final IRulesManager mIRulesManager;
+
+    public RulesManager(Context context) {
+        mContext = context;
+        mIRulesManager = IRulesManager.Stub.asInterface(
+                ServiceManager.getService(Context.TIME_ZONE_RULES_MANAGER_SERVICE));
+    }
+
+    /**
+     * Returns information about the current time zone rules state such as the IANA version of
+     * the system and any currently installed distro. This method is intended to allow clients to
+     * determine if the current state can be improved; for example by passing the information to a
+     * server that may provide a new distro for download.
+     */
+    public RulesState getRulesState() {
+        try {
+            logDebug("sIRulesManager.getRulesState()");
+            RulesState rulesState = mIRulesManager.getRulesState();
+            logDebug("sIRulesManager.getRulesState() returned " + rulesState);
+            return rulesState;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests installation of the supplied distro. The distro must have been checked for integrity
+     * by the caller or have been received via a trusted mechanism.
+     *
+     * @param distroFileDescriptor the file descriptor for the distro
+     * @param checkToken an optional token provided if the install was triggered in response to a
+     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+     * @param callback the {@link Callback} to receive callbacks related to the installation
+     * @return {@link #SUCCESS} if the installation will be attempted
+     */
+    @ResultCode
+    public int requestInstall(
+            ParcelFileDescriptor distroFileDescriptor, byte[] checkToken, Callback callback)
+            throws IOException {
+
+        ICallback iCallback = new CallbackWrapper(mContext, callback);
+        try {
+            logDebug("sIRulesManager.requestInstall()");
+            return mIRulesManager.requestInstall(distroFileDescriptor, checkToken, iCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests uninstallation of the currently installed distro (leaving the device with no
+     * distro installed).
+     *
+     * @param checkToken an optional token provided if the uninstall was triggered in response to a
+     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+     * @param callback the {@link Callback} to receive callbacks related to the uninstall
+     * @return {@link #SUCCESS} if the uninstallation will be attempted
+     */
+    @ResultCode
+    public int requestUninstall(byte[] checkToken, Callback callback) {
+        ICallback iCallback = new CallbackWrapper(mContext, callback);
+        try {
+            logDebug("sIRulesManager.requestUninstall()");
+            return mIRulesManager.requestUninstall(checkToken, iCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /*
+     * We wrap incoming binder calls with a private class implementation that
+     * redirects them into main-thread actions.  This serializes the backup
+     * progress callbacks nicely within the usual main-thread lifecycle pattern.
+     */
+    private class CallbackWrapper extends ICallback.Stub {
+        final Handler mHandler;
+        final Callback mCallback;
+
+        CallbackWrapper(Context context, Callback callback) {
+            mCallback = callback;
+            mHandler = new Handler(context.getMainLooper());
+        }
+
+        // Binder calls into this object just enqueue on the main-thread handler
+        @Override
+        public void onFinished(int status) {
+            logDebug("mCallback.onFinished(status), status=" + status);
+            mHandler.post(() -> mCallback.onFinished(status));
+        }
+    }
+
+    /**
+     * Requests the system does not modify the currently installed time zone distro, if any. This
+     * method records the fact that a time zone check operation triggered by the system is now
+     * complete and there was nothing to do. The token passed should be the one presented when the
+     * check was triggered.
+     *
+     * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
+     * should be careful not to pass false if the failure is unlikely to resolve by itself.
+     *
+     * @param checkToken an optional token provided if the install was triggered in response to a
+     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+     * @param succeeded true if the check was successful, false if it was not successful but may
+     *     succeed if it is retried
+     */
+    public void requestNothing(byte[] checkToken, boolean succeeded) {
+        try {
+            logDebug("sIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken));
+            mIRulesManager.requestNothing(checkToken, succeeded);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    static void logDebug(String msg) {
+        if (DEBUG) {
+            Log.v(TAG, msg);
+        }
+    }
+}
diff --git a/core/java/android/app/timezone/RulesState.aidl b/core/java/android/app/timezone/RulesState.aidl
new file mode 100644
index 0000000..f789120
--- /dev/null
+++ b/core/java/android/app/timezone/RulesState.aidl
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+parcelable RulesState;
\ No newline at end of file
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
new file mode 100644
index 0000000..33f4e80
--- /dev/null
+++ b/core/java/android/app/timezone/RulesState.java
@@ -0,0 +1,319 @@
+/*
+ * 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.app.timezone;
+
+import static android.app.timezone.Utils.validateConditionalNull;
+import static android.app.timezone.Utils.validateNotNull;
+import static android.app.timezone.Utils.validateRulesVersion;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Description of the state of time zone rules on a device.
+ *
+ * <p>The following properties are included:
+ * <dl>
+ *     <dt>systemRulesVersion</dt>
+ *     <dd>the IANA rules version that shipped with the OS. Always present. e.g. "2017a".</dd>
+ *     <dt>distroFormatVersionSupported</dt>
+ *     <dd>the distro format version supported by this device. Always present.</dd>
+ *     <dt>operationInProgress</dt>
+ *     <dd>{@code true} if there is an install / uninstall operation currently happening.</dd>
+ *     <dt>stagedOperationType</dt>
+ *     <dd>one of {@link #STAGED_OPERATION_UNKNOWN}, {@link #STAGED_OPERATION_NONE},
+ *     {@link #STAGED_OPERATION_UNINSTALL} and {@link #STAGED_OPERATION_INSTALL} indicating whether
+ *     there is a currently staged time zone distro operation. {@link #STAGED_OPERATION_UNKNOWN} is
+ *     used when {@link #isOperationInProgress()} is {@code true}. Staged operations currently
+ *     require a reboot to become active.</dd>
+ *     <dt>stagedDistroRulesVersion</dt>
+ *     <dd>[present if distroStagedState == STAGED_STATE_INSTALL], the rules version of the distro
+ *     currently staged for installation.</dd>
+ *     <dt>distroStatus</dt>
+ *     <dd>{@link #DISTRO_STATUS_INSTALLED} if there is a time zone distro installed and active,
+ *     {@link #DISTRO_STATUS_NONE} if there is no active installed distro.
+ *     {@link #DISTRO_STATUS_UNKNOWN} is used when {@link #isOperationInProgress()} is {@code true}.
+ *     </dd>
+ *     <dt>installedDistroRulesVersion</dt>
+ *     <dd>[present if distroStatus == {@link #DISTRO_STATUS_INSTALLED}], the rules version of the
+ *     installed and active distro.</dd>
+ * </dl>
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class RulesState implements Parcelable {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            STAGED_OPERATION_UNKNOWN,
+            STAGED_OPERATION_NONE,
+            STAGED_OPERATION_UNINSTALL,
+            STAGED_OPERATION_INSTALL })
+    private @interface StagedOperationType {}
+
+    /** Staged state could not be determined. */
+    public static final int STAGED_OPERATION_UNKNOWN = 0;
+    /** Nothing is staged. */
+    public static final int STAGED_OPERATION_NONE = 1;
+    /** An uninstall is staged. */
+    public static final int STAGED_OPERATION_UNINSTALL = 2;
+    /** An install is staged. */
+    public static final int STAGED_OPERATION_INSTALL = 3;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DISTRO_STATUS_UNKNOWN,
+            DISTRO_STATUS_NONE,
+            DISTRO_STATUS_INSTALLED })
+    private @interface DistroStatus {}
+
+    /** The current distro status could not be determined. */
+    public static final int DISTRO_STATUS_UNKNOWN = 0;
+    /** There is no active installed time zone distro. */
+    public static final int DISTRO_STATUS_NONE = 1;
+    /** The is an active, installed time zone distro. */
+    public static final int DISTRO_STATUS_INSTALLED = 2;
+
+    private static final byte BYTE_FALSE = 0;
+    private static final byte BYTE_TRUE = 1;
+
+    private final String mSystemRulesVersion;
+    private final DistroFormatVersion mDistroFormatVersionSupported;
+    private final boolean mOperationInProgress;
+    @StagedOperationType private final int mStagedOperationType;
+    @Nullable private final DistroRulesVersion mStagedDistroRulesVersion;
+    @DistroStatus private final int mDistroStatus;
+    @Nullable private final DistroRulesVersion mInstalledDistroRulesVersion;
+
+    public RulesState(String systemRulesVersion, DistroFormatVersion distroFormatVersionSupported,
+            boolean operationInProgress,
+            @StagedOperationType int stagedOperationType,
+            @Nullable DistroRulesVersion stagedDistroRulesVersion,
+            @DistroStatus int distroStatus,
+            @Nullable DistroRulesVersion installedDistroRulesVersion) {
+        this.mSystemRulesVersion = validateRulesVersion("systemRulesVersion", systemRulesVersion);
+        this.mDistroFormatVersionSupported =
+                validateNotNull("distroFormatVersionSupported", distroFormatVersionSupported);
+        this.mOperationInProgress = operationInProgress;
+
+        if (operationInProgress && stagedOperationType != STAGED_OPERATION_UNKNOWN) {
+            throw new IllegalArgumentException(
+                    "stagedOperationType != STAGED_OPERATION_UNKNOWN");
+        }
+        this.mStagedOperationType = validateStagedOperation(stagedOperationType);
+        this.mStagedDistroRulesVersion = validateConditionalNull(
+                mStagedOperationType == STAGED_OPERATION_INSTALL /* requireNotNull */,
+                "stagedDistroRulesVersion", stagedDistroRulesVersion);
+
+        if (operationInProgress && distroStatus != DISTRO_STATUS_UNKNOWN) {
+            throw new IllegalArgumentException("distroInstalled != DISTRO_STATUS_UNKNOWN");
+        }
+        this.mDistroStatus = validateDistroStatus(distroStatus);
+        this.mInstalledDistroRulesVersion = validateConditionalNull(
+                mDistroStatus == DISTRO_STATUS_INSTALLED/* requireNotNull */,
+                "installedDistroRulesVersion", installedDistroRulesVersion);
+    }
+
+    public String getSystemRulesVersion() {
+        return mSystemRulesVersion;
+    }
+
+    public boolean isOperationInProgress() {
+        return mOperationInProgress;
+    }
+
+    public @StagedOperationType int getStagedOperationType() {
+        return mStagedOperationType;
+    }
+
+    /**
+     * Returns the staged rules version when {@link #getStagedOperationType()} is
+     * {@link #STAGED_OPERATION_INSTALL}.
+     */
+    public @Nullable DistroRulesVersion getStagedDistroRulesVersion() {
+        return mStagedDistroRulesVersion;
+    }
+
+    public @DistroStatus int getDistroStatus() {
+        return mDistroStatus;
+    }
+
+    /**
+     * Returns the installed rules version when {@link #getDistroStatus()} is
+     * {@link #DISTRO_STATUS_INSTALLED}.
+     */
+    public @Nullable DistroRulesVersion getInstalledDistroRulesVersion() {
+        return mInstalledDistroRulesVersion;
+    }
+
+    /**
+     * Returns true if a distro in the specified format is supported on this device.
+     */
+    public boolean isDistroFormatVersionSupported(DistroFormatVersion distroFormatVersion) {
+        return mDistroFormatVersionSupported.supports(distroFormatVersion);
+    }
+
+    /**
+     * Returns true if the distro IANA rules version supplied is newer or the same as the version in
+     * the system image data files.
+     */
+    public boolean isSystemVersionOlderThan(DistroRulesVersion distroRulesVersion) {
+        return mSystemRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) < 0;
+    }
+
+    public boolean isDistroInstalled() {
+        return mDistroStatus == DISTRO_STATUS_INSTALLED;
+    }
+
+    /**
+     * Returns true if the rules version supplied is newer than the one currently installed. If
+     * there is no installed distro this method throws IllegalStateException.
+     */
+    public boolean isInstalledDistroOlderThan(DistroRulesVersion distroRulesVersion) {
+        if (mOperationInProgress) {
+            throw new IllegalStateException("Distro state not known: operation in progress.");
+        }
+        if (!isDistroInstalled()) {
+            throw new IllegalStateException("No distro installed.");
+        }
+        return mInstalledDistroRulesVersion.isOlderThan(distroRulesVersion);
+    }
+
+    public static final Parcelable.Creator<RulesState> CREATOR =
+            new Parcelable.Creator<RulesState>() {
+        public RulesState createFromParcel(Parcel in) {
+            return RulesState.createFromParcel(in);
+        }
+
+        public RulesState[] newArray(int size) {
+            return new RulesState[size];
+        }
+    };
+
+    private static RulesState createFromParcel(Parcel in) {
+        String systemRulesVersion = in.readString();
+        DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null);
+        boolean operationInProgress = in.readByte() == BYTE_TRUE;
+        int distroStagedState = in.readByte();
+        DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null);
+        int installedDistroStatus = in.readByte();
+        DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null);
+        return new RulesState(systemRulesVersion, distroFormatVersionSupported, operationInProgress,
+                distroStagedState, stagedDistroRulesVersion,
+                installedDistroStatus, installedDistroRulesVersion);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mSystemRulesVersion);
+        out.writeParcelable(mDistroFormatVersionSupported, 0);
+        out.writeByte(mOperationInProgress ? BYTE_TRUE : BYTE_FALSE);
+        out.writeByte((byte) mStagedOperationType);
+        out.writeParcelable(mStagedDistroRulesVersion, 0);
+        out.writeByte((byte) mDistroStatus);
+        out.writeParcelable(mInstalledDistroRulesVersion, 0);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        RulesState that = (RulesState) o;
+
+        if (mOperationInProgress != that.mOperationInProgress) {
+            return false;
+        }
+        if (mStagedOperationType != that.mStagedOperationType) {
+            return false;
+        }
+        if (mDistroStatus != that.mDistroStatus) {
+            return false;
+        }
+        if (!mSystemRulesVersion.equals(that.mSystemRulesVersion)) {
+            return false;
+        }
+        if (!mDistroFormatVersionSupported.equals(that.mDistroFormatVersionSupported)) {
+            return false;
+        }
+        if (mStagedDistroRulesVersion != null ? !mStagedDistroRulesVersion
+                .equals(that.mStagedDistroRulesVersion) : that.mStagedDistroRulesVersion != null) {
+            return false;
+        }
+        return mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
+                .equals(that.mInstalledDistroRulesVersion)
+                : that.mInstalledDistroRulesVersion == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mSystemRulesVersion.hashCode();
+        result = 31 * result + mDistroFormatVersionSupported.hashCode();
+        result = 31 * result + (mOperationInProgress ? 1 : 0);
+        result = 31 * result + mStagedOperationType;
+        result = 31 * result + (mStagedDistroRulesVersion != null ? mStagedDistroRulesVersion
+                .hashCode()
+                : 0);
+        result = 31 * result + mDistroStatus;
+        result = 31 * result + (mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
+                .hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "RulesState{"
+                + "mSystemRulesVersion='" + mSystemRulesVersion + '\''
+                + ", mDistroFormatVersionSupported=" + mDistroFormatVersionSupported
+                + ", mOperationInProgress=" + mOperationInProgress
+                + ", mStagedOperationType=" + mStagedOperationType
+                + ", mStagedDistroRulesVersion=" + mStagedDistroRulesVersion
+                + ", mDistroStatus=" + mDistroStatus
+                + ", mInstalledDistroRulesVersion=" + mInstalledDistroRulesVersion
+                + '}';
+    }
+
+    private static int validateStagedOperation(int stagedOperationType) {
+        if (stagedOperationType < STAGED_OPERATION_UNKNOWN
+                || stagedOperationType > STAGED_OPERATION_INSTALL) {
+            throw new IllegalArgumentException("Unknown operation type=" + stagedOperationType);
+        }
+        return stagedOperationType;
+    }
+
+    private static int validateDistroStatus(int distroStatus) {
+        if (distroStatus < DISTRO_STATUS_UNKNOWN || distroStatus > DISTRO_STATUS_INSTALLED) {
+            throw new IllegalArgumentException("Unknown distro status=" + distroStatus);
+        }
+        return distroStatus;
+    }
+}
diff --git a/core/java/android/app/timezone/RulesUpdaterContract.java b/core/java/android/app/timezone/RulesUpdaterContract.java
new file mode 100644
index 0000000..4e77818
--- /dev/null
+++ b/core/java/android/app/timezone/RulesUpdaterContract.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Constants related to the contract between the Android system and the privileged time zone updater
+ * application.
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class RulesUpdaterContract {
+
+    /**
+     * The system permission possessed by the Android system that allows it to trigger time zone
+     * update checks. The updater should be configured to require this permission when registering
+     * for {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intents.
+     */
+    public static final String TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION =
+            android.Manifest.permission.TRIGGER_TIME_ZONE_RULES_CHECK;
+
+    /**
+     * The system permission possessed by the time zone rules updater app that allows it to update
+     * device time zone rules. The Android system requires this permission for calls made to
+     * {@link RulesManager}.
+     */
+    public static final String UPDATE_TIME_ZONE_RULES_PERMISSION =
+            android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
+
+    /**
+     * The action of the intent that the Android system will broadcast. The intent will be targeted
+     * at the configured updater application's package meaning the term "broadcast" only loosely
+     * applies.
+     */
+    public static final String ACTION_TRIGGER_RULES_UPDATE_CHECK =
+            "android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
+
+    /**
+     * The extra containing the {@code byte[]} that should be passed to
+     * {@link RulesManager#requestInstall(ParcelFileDescriptor, byte[], Callback)},
+     * {@link RulesManager#requestUninstall(byte[], Callback)} and
+     * {@link RulesManager#requestNothing(byte[], boolean)} methods when the
+     * {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent has been processed.
+     */
+    public static final String EXTRA_CHECK_TOKEN =
+            "android.intent.extra.timezone.CHECK_TOKEN";
+
+    /**
+     * Creates an intent that would trigger a time zone rules update check.
+     */
+    public static Intent createUpdaterIntent(String updaterPackageName) {
+        Intent intent = new Intent(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK);
+        intent.setPackage(updaterPackageName);
+        intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+        return intent;
+    }
+
+    /**
+     * Broadcasts an {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with the
+     * {@link #EXTRA_CHECK_TOKEN} that triggers an update check, including the required receiver
+     * permission.
+     */
+    public static void sendBroadcast(Context context, String updaterAppPackageName,
+            byte[] checkTokenBytes) {
+        Intent intent = createUpdaterIntent(updaterAppPackageName);
+        intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes);
+        context.sendBroadcast(intent, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
+    }
+}
diff --git a/core/java/android/app/timezone/Utils.java b/core/java/android/app/timezone/Utils.java
new file mode 100644
index 0000000..8dd3fb7
--- /dev/null
+++ b/core/java/android/app/timezone/Utils.java
@@ -0,0 +1,67 @@
+/*
+ * 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.app.timezone;
+
+/**
+ * Shared code for android.app.timezone classes.
+ */
+final class Utils {
+    private Utils() {}
+
+    static int validateVersion(String type, int version) {
+        if (version < 0 || version > 999) {
+            throw new IllegalArgumentException("Invalid " + type + " version=" + version);
+        }
+        return version;
+    }
+
+    static String validateRulesVersion(String type, String rulesVersion) {
+        validateNotNull(type, rulesVersion);
+
+        if (rulesVersion.isEmpty()) {
+            throw new IllegalArgumentException(type + " must not be empty");
+        }
+        return rulesVersion;
+    }
+
+    /** Validates that {@code object} is not null. Always returns {@code object}. */
+    static <T> T validateNotNull(String type, T object) {
+        if (object == null) {
+            throw new NullPointerException(type + " == null");
+        }
+        return object;
+    }
+
+    /**
+     * If {@code requireNotNull} is {@code true} calls {@link #validateNotNull(String, Object)},
+     * and {@link #validateNull(String, Object)} otherwise. Returns {@code object}.
+     */
+    static <T> T validateConditionalNull(boolean requireNotNull, String type, T object) {
+        if (requireNotNull) {
+            return validateNotNull(type, object);
+        } else {
+            return validateNull(type, object);
+        }
+    }
+
+    /** Validates that {@code object} is null. Always returns null. */
+    static <T> T validateNull(String type, T object) {
+        if (object != null) {
+            throw new IllegalArgumentException(type + " != null");
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 845a47d..ff52f27 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -466,6 +466,30 @@
         "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
 
     /**
+     * Intent used to broadcast the change in the Bluetooth address
+     * of the local Bluetooth adapter.
+     * <p>Always contains the extra field {@link
+     * #EXTRA_BLUETOOTH_ADDRESS} containing the Bluetooth address.
+     *
+     * Note: only system level processes are allowed to send this
+     * defined broadcast.
+     *
+     * @hide
+     */
+    public static final String ACTION_BLUETOOTH_ADDRESS_CHANGED =
+        "android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED";
+
+    /**
+     * Used as a String extra field in {@link
+     * #ACTION_BLUETOOTH_ADDRESS_CHANGED} intent to store the local
+     * Bluetooth address.
+     *
+     * @hide
+     */
+    public static final String EXTRA_BLUETOOTH_ADDRESS =
+          "android.bluetooth.adapter.extra.BLUETOOTH_ADDRESS";
+
+    /**
      * Broadcast Action: The notifys Bluetooth ACL connected event. This will be
      * by BLE Always on enabled application to know the ACL_CONNECTED event
      * when Bluetooth state in STATE_BLE_ON. This denotes GATT connection
@@ -1008,28 +1032,6 @@
     }
 
     /**
-     * enable or disable Bluetooth HCI snoop log.
-     *
-     * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission
-     *
-     * @return true to indicate configure HCI log successfully, or false on
-     *         immediate error
-     * @hide
-     */
-    public boolean configHciSnoopLog(boolean enable) {
-        try {
-            mServiceLock.readLock().lock();
-            if (mService != null) return mService.configHciSnoopLog(enable);
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-        } finally {
-            mServiceLock.readLock().unlock();
-        }
-        return false;
-    }
-
-    /**
      * Factory reset bluetooth settings.
      *
      * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
@@ -1484,8 +1486,8 @@
     }
 
     /**
-     * Return the maximum LE advertising data length,
-     * if LE Extended Advertising feature is supported.
+     * Return the maximum LE advertising data length in bytes,
+     * if LE Extended Advertising feature is supported, 0 otherwise.
      *
      * @return the maximum LE advertising data length.
      */
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index cb6fa05..f158f5f 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -22,6 +22,8 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.ParcelUuid;
@@ -593,24 +595,40 @@
     public static final int TRANSPORT_LE = 2;
 
     /**
-     * 1M initiating PHY.
+     * Bluetooth LE 1M PHY. Used to refer to LE 1M Physical Channel for advertising, scanning or
+     * connection.
      */
     public static final int PHY_LE_1M = 1;
 
     /**
-     * 2M initiating PHY.
+     * Bluetooth LE 2M PHY. Used to refer to LE 2M Physical Channel for advertising, scanning or
+     * connection.
      */
     public static final int PHY_LE_2M = 2;
 
     /**
-     * LE Coded initiating PHY.
+     * Bluetooth LE Coded PHY. Used to refer to LE Coded Physical Channel for advertising, scanning
+     * or connection.
      */
-    public static final int PHY_LE_CODED = 4;
+    public static final int PHY_LE_CODED = 3;
 
     /**
-     * Any LE PHY.
+     * Bluetooth LE 1M PHY mask. Used to specify LE 1M Physical Channel as one of many available
+     * options in a bitmask.
      */
-    public static final int PHY_LE_ANY = PHY_LE_1M | PHY_LE_2M | PHY_LE_CODED;
+    public static final int PHY_LE_1M_MASK = 1;
+
+    /**
+     * Bluetooth LE 2M PHY mask. Used to specify LE 2M Physical Channel as one of many available
+     * options in a bitmask.
+     */
+    public static final int PHY_LE_2M_MASK = 2;
+
+    /**
+     * Bluetooth LE Coded PHY mask. Used to specify LE Coded Physical Channel as one of many
+     * available options in a bitmask.
+     */
+    public static final int PHY_LE_CODED_MASK = 4;
 
     /**
      * No preferred coding when transmitting on the LE Coded PHY.
@@ -1651,7 +1669,7 @@
      */
     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
                                      BluetoothGattCallback callback, int transport) {
-        return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO, PHY_LE_1M));
+        return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO, PHY_LE_1M_MASK));
     }
 
     /**
@@ -1668,13 +1686,43 @@
      *             {@link BluetoothDevice#TRANSPORT_AUTO} or
      *             {@link BluetoothDevice#TRANSPORT_BREDR} or {@link BluetoothDevice#TRANSPORT_LE}
      * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of
-     *             {@link BluetoothDevice#PHY_LE_1M}, {@link BluetoothDevice#PHY_LE_2M},
-     *             and {@link BluetoothDevice#PHY_LE_CODED}. This option does not take effect if
-     *             {@code autoConnect} is set to true.
-     * @throws IllegalArgumentException if callback is null
+     *             {@link BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK},
+     *             and {@link BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect
+     *             if {@code autoConnect} is set to true.
+     * @throws NullPointerException if callback is null
      */
     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
                                      BluetoothGattCallback callback, int transport, int phy) {
+        return connectGatt(context, autoConnect,callback, TRANSPORT_AUTO, PHY_LE_1M_MASK, null);
+    }
+
+    /**
+     * 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 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.
+     * @throws NullPointerException if callback is null
+     */
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+                                     BluetoothGattCallback callback, int transport, int phy,
+                                     Handler handler) {
+        if (callback == null)
+            throw new NullPointerException("callback is null");
+
         // TODO(Bluetooth) check whether platform support BLE
         //     Do the check here or in GattServer?
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -1686,7 +1734,7 @@
                 return null;
             }
             BluetoothGatt gatt = new BluetoothGatt(iGatt, this, transport, phy);
-            gatt.connect(autoConnect, callback);
+            gatt.connect(autoConnect, callback, handler);
             return gatt;
         } catch (RemoteException e) {Log.e(TAG, "", e);}
         return null;
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 02ba403..b12ff72 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -16,7 +16,7 @@
 
 package android.bluetooth;
 
-import android.content.Context;
+import android.os.Handler;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.util.Log;
@@ -43,6 +43,7 @@
 
     private IBluetoothGatt mService;
     private BluetoothGattCallback mCallback;
+    private Handler mHandler;
     private int mClientIf;
     private BluetoothDevice mDevice;
     private boolean mAutoConnect;
@@ -154,8 +155,16 @@
                 }
                 mClientIf = clientIf;
                 if (status != GATT_SUCCESS) {
-                    mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE,
-                                                      BluetoothProfile.STATE_DISCONNECTED);
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (mCallback != null) {
+                                mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE,
+                                                  BluetoothProfile.STATE_DISCONNECTED);
+                            }
+                        }
+                    });
+
                     synchronized(mStateLock) {
                         mConnState = CONN_STATE_IDLE;
                     }
@@ -181,11 +190,14 @@
                     return;
                 }
 
-                try {
-                    mCallback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -200,11 +212,14 @@
                     return;
                 }
 
-                try {
-                    mCallback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -221,11 +236,16 @@
                 }
                 int profileState = connected ? BluetoothProfile.STATE_CONNECTED :
                                                BluetoothProfile.STATE_DISCONNECTED;
-                try {
-                    mCallback.onConnectionStateChange(BluetoothGatt.this, status, profileState);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onConnectionStateChange(BluetoothGatt.this, status,
+                                                              profileState);
+                        }
+                    }
+                });
 
                 synchronized(mStateLock) {
                     if (connected) {
@@ -279,11 +299,14 @@
                     }
                 }
 
-                try {
-                    mCallback.onServicesDiscovered(BluetoothGatt.this, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onServicesDiscovered(BluetoothGatt.this, status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -328,11 +351,15 @@
 
                 if (status == 0) characteristic.setValue(value);
 
-                try {
-                    mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic,
+                                                           status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -373,11 +400,15 @@
 
                 mAuthRetryState = AUTH_RETRY_STATE_IDLE;
 
-                try {
-                    mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
+                                                            status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -398,11 +429,14 @@
 
                 characteristic.setValue(value);
 
-                try {
-                    mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic);
+                        }
+                    }
+                });
             }
 
             /**
@@ -442,11 +476,14 @@
 
                 mAuthRetryState = AUTH_RETRY_STATE_IDLE;
 
-                try {
-                    mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -485,11 +522,14 @@
 
                 mAuthRetryState = AUTH_RETRY_STATE_IDLE;
 
-                try {
-                    mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -508,11 +548,14 @@
                     mDeviceBusy = false;
                 }
 
-                try {
-                    mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                           mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -526,11 +569,14 @@
                 if (!address.equals(mDevice.getAddress())) {
                     return;
                 }
-                try {
-                    mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -544,11 +590,15 @@
                 if (!address.equals(mDevice.getAddress())) {
                     return;
                 }
-                try {
-                    mCallback.onMtuChanged(BluetoothGatt.this, mtu, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onMtuChanged(BluetoothGatt.this, mtu, status);
+                        }
+                    }
+                });
             }
 
             /**
@@ -564,12 +614,16 @@
                 if (!address.equals(mDevice.getAddress())) {
                     return;
                 }
-                try {
-                    mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
-                                                  timeout, status);
-                } catch (Exception ex) {
-                    Log.w(TAG, "Unhandled exception in callback", ex);
-                }
+
+                runOrQueueCallback(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mCallback != null) {
+                            mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
+                                                          timeout, status);
+                        }
+                    }
+                });
             }
         };
 
@@ -648,6 +702,22 @@
     }
 
     /**
+     * Queue the runnable on a {@link Handler} provided by the user, or execute the runnable
+     * immediately if no Handler was provided.
+     */
+    private void runOrQueueCallback(final Runnable cb) {
+        if (mHandler == null) {
+          try {
+            cb.run();
+          } catch (Exception ex) {
+            Log.w(TAG, "Unhandled exception in callback", ex);
+          }
+        } else {
+          mHandler.post(cb);
+        }
+    }
+
+    /**
      * Register an application callback to start using GATT.
      *
      * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
@@ -659,11 +729,12 @@
      * @return If true, the callback will be called to notify success or failure,
      *         false on immediate error
      */
-    private boolean registerApp(BluetoothGattCallback callback) {
+    private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
         if (DBG) Log.d(TAG, "registerApp()");
         if (mService == null) return false;
 
         mCallback = callback;
+        mHandler = handler;
         UUID uuid = UUID.randomUUID();
         if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
 
@@ -716,7 +787,8 @@
      *                    device becomes available (true).
      * @return true, if the connection attempt was initiated successfully
      */
-    /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback) {
+    /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback,
+                                Handler handler) {
         if (DBG) Log.d(TAG, "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect);
         synchronized(mStateLock) {
             if (mConnState != CONN_STATE_IDLE) {
@@ -727,7 +799,7 @@
 
         mAutoConnect = autoConnect;
 
-        if (!registerApp(callback)) {
+        if (!registerApp(callback, handler)) {
             synchronized(mStateLock) {
                 mConnState = CONN_STATE_IDLE;
             }
@@ -785,11 +857,11 @@
      * if no PHY change happens. It is also triggered when remote device updates the PHY.
      *
      * @param txPhy preferred transmitter PHY. Bitwise OR of any of
-     *             {@link BluetoothDevice#PHY_LE_1M}, {@link BluetoothDevice#PHY_LE_2M}, and
-     *             {@link BluetoothDevice#PHY_LE_CODED}.
+     *             {@link BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK},
+     *             and {@link BluetoothDevice#PHY_LE_CODED_MASK}.
      * @param rxPhy preferred receiver PHY. Bitwise OR of any of
-     *             {@link BluetoothDevice#PHY_LE_1M}, {@link BluetoothDevice#PHY_LE_2M}, and
-     *             {@link BluetoothDevice#PHY_LE_CODED}.
+     *             {@link BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK},
+     *             and {@link BluetoothDevice#PHY_LE_CODED_MASK}.
      * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
      *             of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED},
      *             {@link BluetoothDevice#PHY_OPTION_S2} or {@link BluetoothDevice#PHY_OPTION_S8}
@@ -854,6 +926,31 @@
     }
 
     /**
+     * Discovers a service by UUID. This is exposed only for passing PTS tests.
+     * It should never be used by real applications. The service is not searched
+     * for characteristics and descriptors, or returned in any callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return true, if the remote service discovery has been started
+     * @hide
+     */
+    public boolean discoverServiceByUuid(UUID uuid) {
+        if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        mServices.clear();
+
+        try {
+            mService.discoverServiceByUuid(mClientIf, mDevice.getAddress(), new ParcelUuid(uuid));
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * Returns a list of GATT services offered by the remote device.
      *
      * <p>This function requires that service discovery has been completed
@@ -947,6 +1044,41 @@
     }
 
     /**
+     * Reads the characteristic using its UUID from the associated remote device.
+     *
+     * <p>This is an asynchronous operation. The result of the read operation
+     * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
+     * callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param uuid UUID of characteristic to read from the remote device
+     * @return true, if the read operation was initiated successfully
+     * @hide
+     */
+    public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) {
+        if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid);
+        if (mService == null || mClientIf == 0) return false;
+
+        synchronized(mDeviceBusy) {
+            if (mDeviceBusy) return false;
+            mDeviceBusy = true;
+        }
+
+        try {
+            mService.readUsingCharacteristicUuid(mClientIf, mDevice.getAddress(),
+                new ParcelUuid(uuid), startHandle, endHandle, AUTHENTICATION_NONE);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            mDeviceBusy = false;
+            return false;
+        }
+
+        return true;
+    }
+
+
+    /**
      * Writes a given characteristic and its values to the associated remote device.
      *
      * <p>Once the write operation has been completed, the
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
index 11a15c6..c6f82ff 100644
--- a/core/java/android/bluetooth/BluetoothGattCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -30,7 +30,8 @@
      *             {@link BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}.
      * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M},
      *             {@link BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}.
-     * @param status status of the operation
+     * @param status Status of the PHY update operation.
+     *                  {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
      */
     public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
     }
@@ -43,7 +44,8 @@
      *             {@link BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}.
      * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M},
      *             {@link BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}.
-     * @param status status of the operation
+     * @param status Status of the PHY read operation.
+     *                  {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
      */
     public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
     }
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 2df2ed8..eddc278 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -558,11 +558,11 @@
      *
      * @param device The remote device to send this response to
      * @param txPhy preferred transmitter PHY. Bitwise OR of any of
-     *             {@link BluetoothDevice#PHY_LE_1M}, {@link BluetoothDevice#PHY_LE_2M}, and
-     *             {@link BluetoothDevice#PHY_LE_CODED}.
+     *             {@link BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK},
+     *             and {@link BluetoothDevice#PHY_LE_CODED_MASK}.
      * @param rxPhy preferred receiver PHY. Bitwise OR of any of
-     *             {@link BluetoothDevice#PHY_LE_1M}, {@link BluetoothDevice#PHY_LE_2M}, and
-     *             {@link BluetoothDevice#PHY_LE_CODED}.
+     *             {@link BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK},
+     *             and {@link BluetoothDevice#PHY_LE_CODED_MASK}.
      * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
      *             of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED},
      *             {@link BluetoothDevice#PHY_OPTION_S2} or {@link BluetoothDevice#PHY_OPTION_S8}
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index 3b8f962..02307bd 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -167,7 +167,8 @@
      *             {@link BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}
      * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M},
      *             {@link BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}
-     * @param status status of the operation
+     * @param status Status of the PHY update operation.
+     *                  {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
      */
     public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
     }
@@ -180,7 +181,8 @@
      *             {@link BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}
      * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M},
      *             {@link BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}
-     * @param status status of the operation
+     * @param status Status of the PHY read operation.
+     *                  {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
      */
     public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
     }
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index f46a3b3..2d25659 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -1010,6 +1010,18 @@
     }
 
     /**
+     * check if in-band ringing is supported for this platform.
+     *
+     * @return true if in-band ringing is supported
+     *         false if in-band ringing is not supported
+     * @hide
+     */
+    public static boolean isInbandRingingSupported(Context context) {
+        return context.getResources().getBoolean(
+                com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
+    }
+
+    /**
      * Send Headset the BIND response from AG to report change in the status of the
      * HF indicators to the headset
      *
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index 252e3d2..fccdf14 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -96,6 +96,12 @@
     public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
         "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
 
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_IDLE_TIME_CHANGED =
+        "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED";
 
     /**
      * Return codes for the connect and disconnect Bluez / Dbus calls.
@@ -199,6 +205,11 @@
      */
     public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS";
 
+    /**
+     * @hide
+     */
+    public static final String EXTRA_IDLE_TIME = "android.bluetooth.BluetoothInputDevice.extra.IDLE_TIME";
+
     private Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
@@ -658,6 +669,56 @@
         if (mService == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
+
+    /**
+     * Send Get_Idle_Time command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error,
+     *               true otherwise
+     * @hide
+     */
+    public boolean getIdleTime(BluetoothDevice device) {
+        if (DBG) log("getIdletime(" + device + ")");
+        if (mService != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return mService.getIdleTime(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Set_Idle_Time command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @param idleTime Idle time to be set on HID Device
+     * @return false on immediate error,
+     *               true otherwise
+     * @hide
+     */
+    public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
+        if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
+        if (mService != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return mService.setIdleTime(device, idleTime);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
     private static void log(String msg) {
       Log.d(TAG, msg);
     }
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index b337817..43c5ae4 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -97,7 +97,6 @@
     ParcelFileDescriptor connectSocket(in BluetoothDevice device, int type, in ParcelUuid uuid, int port, int flag);
     ParcelFileDescriptor createSocketChannel(int type, in String serviceName, in ParcelUuid uuid, int port, int flag);
 
-    boolean configHciSnoopLog(boolean enable);
     boolean factoryReset();
 
     boolean isMultiAdvertisementSupported();
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 334e88b..4ff5976 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -31,7 +31,6 @@
 
 import android.bluetooth.IBluetoothGattCallback;
 import android.bluetooth.IBluetoothGattServerCallback;
-import android.bluetooth.le.IAdvertiserCallback;
 import android.bluetooth.le.IAdvertisingSetCallback;
 import android.bluetooth.le.IPeriodicAdvertisingCallback;
 import android.bluetooth.le.IScannerCallback;
@@ -43,10 +42,10 @@
 interface IBluetoothGatt {
     List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
 
-    void registerScanner(in IScannerCallback callback);
+    void registerScanner(in IScannerCallback callback, in WorkSource workSource);
     void unregisterScanner(in int scannerId);
     void startScan(in int scannerId, in ScanSettings settings, in List<ScanFilter> filters,
-                   in WorkSource workSource, in List scanStorages, in String callingPackage);
+                   in List scanStorages, in String callingPackage);
     void stopScan(in int scannerId);
     void flushPendingBatchResults(in int scannerId);
 
@@ -56,6 +55,7 @@
                                 in IAdvertisingSetCallback callback);
     void stopAdvertisingSet(in IAdvertisingSetCallback callback);
 
+    void getOwnAddress(in int advertiserId);
     void enableAdvertisingSet(in int advertiserId, in boolean enable, in int duration, in int maxExtAdvEvents);
     void setAdvertisingData(in int advertiserId, in AdvertiseData data);
     void setScanResponseData(in int advertiserId, in AdvertiseData data);
@@ -76,7 +76,10 @@
     void clientReadPhy(in int clientIf, in String address);
     void refreshDevice(in int clientIf, in String address);
     void discoverServices(in int clientIf, in String address);
+    void discoverServiceByUuid(in int clientIf, in String address, in ParcelUuid uuid);
     void readCharacteristic(in int clientIf, in String address, in int handle, in int authReq);
+    void readUsingCharacteristicUuid(in int clientIf, in String address, in ParcelUuid uuid,
+                           in int startHandle, in int endHandle, in int authReq);
     void writeCharacteristic(in int clientIf, in String address, in int handle,
                             in int writeType, in int authReq, in byte[] value);
     void readDescriptor(in int clientIf, in String address, in int handle, in int authReq);
diff --git a/core/java/android/bluetooth/IBluetoothInputDevice.aidl b/core/java/android/bluetooth/IBluetoothInputDevice.aidl
index 1ebb9ca..5bd3f78 100644
--- a/core/java/android/bluetooth/IBluetoothInputDevice.aidl
+++ b/core/java/android/bluetooth/IBluetoothInputDevice.aidl
@@ -56,4 +56,12 @@
     * @hide
     */
     boolean sendData(in BluetoothDevice device, String report);
+    /**
+    * @hide
+    */
+    boolean getIdleTime(in BluetoothDevice device);
+    /**
+    * @hide
+    */
+    boolean setIdleTime(in BluetoothDevice device, byte idleTime);
 }
diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java
index 51571b2..1bc211c 100644
--- a/core/java/android/bluetooth/le/AdvertisingSet.java
+++ b/core/java/android/bluetooth/le/AdvertisingSet.java
@@ -172,7 +172,7 @@
      *
      * @param enable whether the periodic advertising should be enabled (true), or disabled (false).
      */
-    public void setPeriodicAdvertisingEnable(boolean enable) {
+    public void setPeriodicAdvertisingEnabled(boolean enable) {
         try {
             gatt.setPeriodicAdvertisingEnable(this.advertiserId, enable);
         } catch (RemoteException e) {
@@ -181,7 +181,23 @@
     }
 
     /**
-     * Returns advertiserId associated with thsi advertising set.
+     * Returns address associated with this advertising set.
+     * This method is exposed only for Bluetooth PTS tests, no app or system service
+     * should ever use it.
+     *
+     * This method requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission.
+     * @hide
+     */
+    public void getOwnAddress(){
+        try {
+            gatt.getOwnAddress(this.advertiserId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Returns advertiserId associated with this advertising set.
      *
      * @hide
      */
diff --git a/core/java/android/bluetooth/le/AdvertisingSetCallback.java b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
index fe3b1cd..c3c16a4 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetCallback.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
@@ -135,7 +135,7 @@
                                              int status) {}
 
     /**
-     * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnable}
+     * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnabled}
      * indicating result of the operation.
      *
      * @param advertisingSet The advertising set.
@@ -143,4 +143,15 @@
      */
     public void onPeriodicAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable,
                                             int status) {}
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#getOwnAddress()}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param addressType type of address.
+     * @param address advertising set bluetooth address.
+     * @hide
+     */
+    public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, String address) {}
 }
\ No newline at end of file
diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index 4e9fac3..e9747d8 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -17,6 +17,7 @@
 package android.bluetooth.le;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -30,25 +31,10 @@
 public final class AdvertisingSetParameters implements Parcelable {
 
     /**
-     * 1M advertiser PHY.
-     */
-    public static final int PHY_LE_1M = 1;
-
-    /**
-     * 2M advertiser PHY.
-     */
-    public static final int PHY_LE_2M = 2;
-
-    /**
-     * LE Coded advertiser PHY.
-     */
-    public static final int PHY_LE_CODED = 3;
-
-    /**
     * Advertise on low frequency, around every 1000ms. This is the default and
     * preferred advertising mode as it consumes the least power.
     */
-    public static final int INTERVAL_LOW = 1600;
+    public static final int INTERVAL_HIGH = 1600;
 
     /**
      * Advertise on medium frequency, around every 250ms. This is balanced
@@ -61,7 +47,7 @@
      * has the highest power consumption and should not be used for continuous
      * background advertising.
      */
-    public static final int INTERVAL_HIGH = 160;
+    public static final int INTERVAL_LOW = 160;
 
     /**
      * Minimum value for advertising interval.
@@ -246,8 +232,8 @@
         private boolean isLegacy = false;
         private boolean isAnonymous = false;
         private boolean includeTxPower = false;
-        private int primaryPhy = PHY_LE_1M;
-        private int secondaryPhy = PHY_LE_1M;
+        private int primaryPhy = BluetoothDevice.PHY_LE_1M;
+        private int secondaryPhy = BluetoothDevice.PHY_LE_1M;
         private int interval = INTERVAL_LOW;
         private int txPowerLevel = TX_POWER_MEDIUM;
 
@@ -256,7 +242,7 @@
          * non-connectable.
          * Legacy advertisements can be both connectable and scannable. Non-legacy
          * advertisements can be only scannable or only connectable.
-         * @param connectable Controls whether the advertisment type will be
+         * @param connectable Controls whether the advertisement type will be
          * connectable (true) or non-connectable (false).
          */
         public Builder setConnectable(boolean connectable) {
@@ -268,7 +254,7 @@
          * Set whether the advertisement type should be scannable.
          * Legacy advertisements can be both connectable and scannable. Non-legacy
          * advertisements can be only scannable or only connectable.
-         * @param scannable Controls whether the advertisment type will be
+         * @param scannable Controls whether the advertisement type will be
          * scannable (true) or non-scannable (false).
          */
         public Builder setScannable(boolean scannable) {
@@ -321,12 +307,13 @@
          * Use {@link BluetoothAdapter#isLeCodedPhySupported} to determine if LE Coded PHY is
          * supported on this device.
          * @param primaryPhy Primary advertising physical channel, can only be
-         *            {@link AdvertisingSetParameters#PHY_LE_1M} or
-         *            {@link AdvertisingSetParameters#PHY_LE_CODED}.
+         *            {@link BluetoothDevice#PHY_LE_1M} or
+         *            {@link BluetoothDevice#PHY_LE_CODED}.
          * @throws IllegalArgumentException If the primaryPhy is invalid.
          */
         public Builder setPrimaryPhy(int primaryPhy) {
-            if (primaryPhy != PHY_LE_1M && primaryPhy != PHY_LE_CODED) {
+            if (primaryPhy != BluetoothDevice.PHY_LE_1M &&
+                primaryPhy != BluetoothDevice.PHY_LE_CODED) {
                throw new IllegalArgumentException("bad primaryPhy " + primaryPhy);
             }
             this.primaryPhy = primaryPhy;
@@ -343,14 +330,15 @@
          * supported on this device.
          *
          * @param secondaryPhy Secondary advertising physical channel, can only be
-         *            one of {@link AdvertisingSetParameters#PHY_LE_1M},
-         *            {@link AdvertisingSetParameters#PHY_LE_2M} or
-         *            {@link AdvertisingSetParameters#PHY_LE_CODED}.
+         *            one of {@link BluetoothDevice#PHY_LE_1M},
+         *            {@link BluetoothDevice#PHY_LE_2M} or
+         *            {@link BluetoothDevice#PHY_LE_CODED}.
          * @throws IllegalArgumentException If the secondaryPhy is invalid.
          */
         public Builder setSecondaryPhy(int secondaryPhy) {
-            if (secondaryPhy != PHY_LE_1M && secondaryPhy !=PHY_LE_2M &&
-                secondaryPhy != PHY_LE_CODED) {
+            if (secondaryPhy != BluetoothDevice.PHY_LE_1M &&
+                secondaryPhy != BluetoothDevice.PHY_LE_2M &&
+                secondaryPhy != BluetoothDevice.PHY_LE_CODED) {
                throw new IllegalArgumentException("bad secondaryPhy " + secondaryPhy);
             }
             this.secondaryPhy = secondaryPhy;
@@ -398,6 +386,7 @@
 
         /**
          * Build the {@link AdvertisingSetParameters} object.
+         * @throws IllegalStateException if invalid combination of parameters is used.
          */
         public AdvertisingSetParameters build() {
             if (isLegacy) {
@@ -406,22 +395,22 @@
                 }
 
                 if (connectable == true && scannable == false) {
-                    throw new IllegalArgumentException(
+                    throw new IllegalStateException(
                         "Legacy advertisement can't be connectable and non-scannable");
                 }
 
                 if (includeTxPower) {
-                    throw new IllegalArgumentException(
+                    throw new IllegalStateException(
                         "Legacy advertising can't include TX power level in header");
                 }
             } else {
                 if (connectable && scannable) {
-                    throw new IllegalArgumentException(
+                    throw new IllegalStateException(
                         "Advertising can't be both connectable and scannable");
                 }
 
                 if (isAnonymous && connectable) {
-                    throw new IllegalArgumentException(
+                    throw new IllegalStateException(
                         "Advertising can't be both connectable and anonymous");
                 }
             }
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index a9deb75..67d56d5 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -17,11 +17,11 @@
 package android.bluetooth.le;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothGatt;
 import android.bluetooth.IBluetoothManager;
-import android.bluetooth.le.IAdvertiserCallback;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelUuid;
@@ -363,12 +363,12 @@
             boolean support2MPhy = mBluetoothAdapter.isLe2MPhySupported();
             int pphy = parameters.getPrimaryPhy();
             int sphy = parameters.getSecondaryPhy();
-            if (pphy == AdvertisingSetParameters.PHY_LE_CODED && !supportCodedPhy) {
+            if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) {
                 throw new IllegalArgumentException("Unsupported primary PHY selected");
             }
 
-            if ((sphy == AdvertisingSetParameters.PHY_LE_CODED && !supportCodedPhy)
-                || (sphy == AdvertisingSetParameters.PHY_LE_2M && !support2MPhy)) {
+            if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy)
+                || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) {
                 throw new IllegalArgumentException("Unsupported secondary PHY selected");
             }
 
@@ -386,7 +386,7 @@
             }
 
             boolean supportPeriodic = mBluetoothAdapter.isLePeriodicAdvertisingSupported();
-            if (periodicParameters != null && periodicParameters.getEnable() && !supportPeriodic) {
+            if (periodicParameters != null && !supportPeriodic) {
                 throw new IllegalArgumentException(
                     "Controller does not support LE Periodic Advertising");
             }
@@ -544,6 +544,17 @@
             }
 
             @Override
+            public void onOwnAddressRead(int advertiserId, int addressType, String address) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onOwnAddressRead(advertisingSet, addressType, address);
+                    }
+                });
+            }
+
+            @Override
             public void onAdvertisingSetStopped(int advertiserId) {
                 handler.post(new Runnable() {
                     @Override
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index b63c614..35c526f 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -300,7 +300,7 @@
                 // Scan stopped.
                 if (mScannerId == -1) return;
                 try {
-                    mBluetoothGatt.registerScanner(this);
+                    mBluetoothGatt.registerScanner(this, mWorkSource);
                     wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS);
                 } catch (InterruptedException | RemoteException e) {
                     Log.e(TAG, "application registeration exception", e);
@@ -364,7 +364,7 @@
                         } else {
                             mScannerId = scannerId;
                             mBluetoothGatt.startScan(mScannerId, mSettings, mFilters,
-                                    mWorkSource, mResultStorages,
+                                    mResultStorages,
                                     ActivityThread.currentOpPackageName());
                         }
                     } catch (RemoteException e) {
diff --git a/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
index 2c9f4ba..3628c77 100644
--- a/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
+++ b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
@@ -21,6 +21,7 @@
  */
 oneway interface IAdvertisingSetCallback {
   void onAdvertisingSetStarted(in int advertiserId, in int tx_power, in int status);
+  void onOwnAddressRead(in int advertiserId, in int addressType, in String address);
   void onAdvertisingSetStopped(in int advertiserId);
   void onAdvertisingEnabled(in int advertiserId, in boolean enable, in int status);
   void onAdvertisingDataSet(in int advertiserId, in int status);
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
index 149540c..cf8f08f 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
@@ -26,31 +26,23 @@
  */
 public final class PeriodicAdvertisingParameters implements Parcelable {
 
-    private static final int INTERVAL_MAX = 80;
-    private static final int INTERVAL_MIN = 65519;
+    private static final int INTERVAL_MIN = 80;
+    private static final int INTERVAL_MAX = 65519;
 
-    private final boolean enable;
     private final boolean includeTxPower;
     private final int interval;
 
-    private PeriodicAdvertisingParameters(boolean enable, boolean includeTxPower, int interval) {
-        this.enable = enable;
+    private PeriodicAdvertisingParameters(boolean includeTxPower, int interval) {
         this.includeTxPower = includeTxPower;
         this.interval = interval;
     }
 
     private PeriodicAdvertisingParameters(Parcel in) {
-        enable = in.readInt() != 0 ? true : false;
         includeTxPower = in.readInt() != 0 ? true : false;
         interval = in.readInt();
     }
 
     /**
-     * Returns whether the periodic advertising shall be enabled.
-     */
-    public boolean getEnable() { return enable; }
-
-    /**
      * Returns whether the TX Power will be included.
      */
     public boolean getIncludeTxPower() { return includeTxPower; }
@@ -68,7 +60,6 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(enable ? 1 : 0);
         dest.writeInt(includeTxPower ? 1 : 0);
         dest.writeInt(interval);
     }
@@ -89,18 +80,9 @@
 
     public static final class Builder {
         private boolean includeTxPower = false;
-        private boolean enable = false;
         private int interval = INTERVAL_MAX;
 
         /**
-         * Set whether the Periodic Advertising should be enabled for this set.
-         */
-        public Builder setEnable(boolean enable) {
-            this.enable = enable;
-            return this;
-        }
-
-        /**
          * Whether the transmission power level should be included in the periodic
          * packet.
          */
@@ -128,7 +110,7 @@
          * Build the {@link AdvertisingSetParameters} object.
          */
         public PeriodicAdvertisingParameters build() {
-            return new PeriodicAdvertisingParameters(enable, includeTxPower, interval);
+            return new PeriodicAdvertisingParameters(includeTxPower, interval);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java
index 745cd16..e552398 100644
--- a/core/java/android/bluetooth/le/ScanResult.java
+++ b/core/java/android/bluetooth/le/ScanResult.java
@@ -47,26 +47,21 @@
     public static final int PHY_UNUSED = 0x00;
 
     /**
-     * Bluetooth LE 1Mbit advertiser PHY.
-     */
-    public static final int PHY_LE_1M = 0x01;
-
-    /**
-     * Bluetooth LE 2Mbit advertiser PHY.
-     */
-    public static final int PHY_LE_2M = 0x02;
-
-    /**
-     * Bluetooth LE Coded advertiser PHY.
-     */
-    public static final int PHY_LE_CODED = 0x03;
-
-    /**
      * Advertising Set ID is not present in the packet.
      */
     public static final int SID_NOT_PRESENT = 0xFF;
 
     /**
+     * TX power is not present in the packet.
+     */
+    public static final int TX_POWER_NOT_PRESENT = 0x7F;
+
+    /**
+     * Periodic advertising interval is not present in the packet.
+     */
+    public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0x00;
+
+    /**
      * Mask for checking whether event type represents legacy advertisement.
      */
     private static final int ET_LEGACY_MASK = 0x10;
@@ -112,7 +107,7 @@
         mRssi = rssi;
         mTimestampNanos = timestampNanos;
         mEventType = (DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK;
-        mPrimaryPhy = PHY_LE_1M;
+        mPrimaryPhy = BluetoothDevice.PHY_LE_1M;
         mSecondaryPhy = PHY_UNUSED;
         mAdvertisingSid = SID_NOT_PRESENT;
         mTxPower = 127;
@@ -256,16 +251,16 @@
     /**
      * Returns the primary Physical Layer
      * on which this advertisment was received.
-     * Can be one of {@link ScanResult#PHY_LE_1M} or
-     * {@link ScanResult#PHY_LE_CODED}.
+     * Can be one of {@link BluetoothDevice#PHY_LE_1M} or
+     * {@link BluetoothDevice#PHY_LE_CODED}.
      */
     public int getPrimaryPhy() { return mPrimaryPhy; }
 
     /**
      * Returns the secondary Physical Layer
      * on which this advertisment was received.
-     * Can be one of {@link ScanResult#PHY_LE_1M},
-     * {@link ScanResult#PHY_LE_2M}, {@link ScanResult#PHY_LE_CODED}
+     * Can be one of {@link BluetoothDevice#PHY_LE_1M},
+     * {@link BluetoothDevice#PHY_LE_2M}, {@link BluetoothDevice#PHY_LE_CODED}
      * or {@link ScanResult#PHY_UNUSED} - if the advertisement
      * was not received on a secondary physical channel.
      */
@@ -280,15 +275,16 @@
 
     /**
      * Returns the transmit power in dBm.
-     * Valid range is [-127, 126]. A value of 127 indicates that the
-     * advertisement did not indicate TX power.
+     * Valid range is [-127, 126]. A value of {@link ScanResult#TX_POWER_NOT_PRESENT}
+     * indicates that the TX power is not present.
      */
     public int getTxPower() { return mTxPower; }
 
     /**
      * Returns the periodic advertising interval in units of 1.25ms.
-     * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of 0 means
-     * periodic advertising is not used for this scan result.
+     * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of
+     * {@link ScanResult#PERIODIC_INTERVAL_NOT_PRESENT} means periodic
+     * advertising interval is not present.
      */
     public int getPeriodicAdvertisingInterval() {
         return mPeriodicAdvertisingInterval;
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
index 69c9a8c..36e48e9 100644
--- a/core/java/android/bluetooth/le/ScanSettings.java
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -17,6 +17,7 @@
 package android.bluetooth.le;
 
 import android.annotation.SystemApi;
+import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -123,16 +124,6 @@
     public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1;
 
     /**
-     * Use the Bluetooth LE 1Mbit PHY for scanning.
-     */
-    public static final int PHY_LE_1M = 1;
-
-    /**
-     * Use Bluetooth LE Coded PHY for scanning.
-     */
-    public static final int PHY_LE_CODED = 3;
-
-    /**
      * Use all supported PHYs for scanning.
      * This will check the controller capabilities, and start
      * the scan on 1Mbit and LE Coded PHYs if supported, or on
@@ -412,8 +403,8 @@
          * Selecting an unsupported phy will result in failure to start scan.
          *
          * @param phy Can be one of
-         *   {@link ScanSettings#PHY_LE_1M},
-         *   {@link ScanSettings#PHY_LE_CODED} or
+         *   {@link BluetoothDevice#PHY_LE_1M},
+         *   {@link BluetoothDevice#PHY_LE_CODED} or
          *   {@link ScanSettings#PHY_LE_ALL_SUPPORTED}
          */
         public Builder setPhy(int phy) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fed36b0..b2f2cb7 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2656,6 +2656,7 @@
             SENSOR_SERVICE,
             STORAGE_SERVICE,
             WALLPAPER_SERVICE,
+            TIME_ZONE_RULES_MANAGER_SERVICE,
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
             CONNECTIVITY_SERVICE,
@@ -2761,9 +2762,6 @@
      *  <dt> {@link #CONNECTIVITY_SERVICE} ("connection")
      *  <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
      *  handling management of network connections.
-     *  <dt> {@link #IPSEC_SERVICE} ("ipsec")
-     *  <dd> A {@link android.net.IpSecManager IpSecManager} for managing IPSec on
-     *  sockets and networks.
      *  <dt> {@link #WIFI_SERVICE} ("wifi")
      *  <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi
      *  connectivity.  On releases before NYC, it should only be obtained from an application
@@ -3098,6 +3096,7 @@
      * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
      * IPSec.
      *
+     * @hide
      * @see #getSystemService
      */
     public static final String IPSEC_SERVICE = "ipsec";
@@ -3668,6 +3667,15 @@
     public static final String GATEKEEPER_SERVICE = "android.service.gatekeeper.IGateKeeperService";
 
     /**
+     * Use with {@link #getSystemService} to retrieve an
+     * {@link android.app.timezone.ITimeZoneRulesManager}.
+     * @hide
+     *
+     * @see #getSystemService
+     */
+    public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 14bb923..f079647 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -755,8 +755,9 @@
             if (!(obj instanceof BandConfig))
                 return false;
             BandConfig other = (BandConfig) obj;
-            if (mDescriptor != other.getDescriptor())
-                return false;
+            BandDescriptor otherDesc = other.getDescriptor();
+            if ((mDescriptor == null) != (otherDesc == null)) return false;
+            if (mDescriptor != null && !mDescriptor.equals(otherDesc)) return false;
             return true;
         }
     }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2a985e7..9e8acd0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,10 +15,9 @@
  */
 package android.net;
 
-import static com.android.internal.util.Preconditions.checkNotNull;
-
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -41,25 +40,27 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.Protocol;
-import com.android.internal.util.MessageUtils;
 
 import libcore.net.event.NetworkEventDispatcher;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
+import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Class that answers queries about the state of network connectivity. It also
@@ -301,7 +302,8 @@
     /**
      * Broadcast Action: A tetherable connection has come or gone.
      * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER},
-     * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER} and
+     * {@code ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY},
+     * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER}, and
      * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate
      * the current state of tethering.  Each include a list of
      * interface names in that state (may be empty).
@@ -320,10 +322,17 @@
 
     /**
      * @hide
-     * gives a String[] listing all the interfaces currently tethered
-     * (ie, has dhcp support and packets potentially forwarded/NATed)
+     * gives a String[] listing all the interfaces currently in local-only
+     * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
      */
-    public static final String EXTRA_ACTIVE_TETHER = "activeArray";
+    public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray";
+
+    /**
+     * @hide
+     * gives a String[] listing all the interfaces currently tethered
+     * (ie, has DHCPv4 support and packets potentially forwarded/NATed)
+     */
+    public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
 
     /**
      * @hide
@@ -1457,9 +1466,7 @@
         // Map from type to transports.
         final int NOT_FOUND = -1;
         final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND);
-        if (transport == NOT_FOUND) {
-            throw new IllegalArgumentException("unknown legacy type: " + type);
-        }
+        Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type);
         nc.addTransportType(transport);
 
         // Map from type to capabilities.
@@ -1547,8 +1554,8 @@
         }
 
         private PacketKeepalive(Network network, PacketKeepaliveCallback callback) {
-            checkNotNull(network, "network cannot be null");
-            checkNotNull(callback, "callback cannot be null");
+            Preconditions.checkNotNull(network, "network cannot be null");
+            Preconditions.checkNotNull(callback, "callback cannot be null");
             mNetwork = network;
             mCallback = callback;
             HandlerThread thread = new HandlerThread(TAG);
@@ -1805,9 +1812,7 @@
      */
     public void removeDefaultNetworkActiveListener(OnNetworkActiveListener l) {
         INetworkActivityListener rl = mNetworkActivityListeners.get(l);
-        if (rl == null) {
-            throw new IllegalArgumentException("Listener not registered: " + l);
-        }
+        Preconditions.checkArgument(rl != null, "Listener was not registered.");
         try {
             getNetworkManagementService().unregisterNetworkActivityListener(rl);
         } catch (RemoteException e) {
@@ -1835,8 +1840,8 @@
      * {@hide}
      */
     public ConnectivityManager(Context context, IConnectivityManager service) {
-        mContext = checkNotNull(context, "missing context");
-        mService = checkNotNull(service, "missing IConnectivityManager");
+        mContext = Preconditions.checkNotNull(context, "missing context");
+        mService = Preconditions.checkNotNull(service, "missing IConnectivityManager");
         sInstance = this;
     }
 
@@ -1862,8 +1867,11 @@
                 .getPackageNameForUid(context, uid), true /* throwException */);
     }
 
-    /** {@hide */
-    public static final void enforceTetherChangePermission(Context context) {
+    /** {@hide} */
+    public static final void enforceTetherChangePermission(Context context, String callingPkg) {
+        Preconditions.checkNotNull(context, "Context cannot be null");
+        Preconditions.checkNotNull(callingPkg, "callingPkg cannot be null");
+
         if (context.getResources().getStringArray(
                 com.android.internal.R.array.config_mobile_hotspot_provision_app).length == 2) {
             // Have a provisioning app - must only let system apps (which check this app)
@@ -1872,8 +1880,10 @@
                     android.Manifest.permission.TETHER_PRIVILEGED, "ConnectivityService");
         } else {
             int uid = Binder.getCallingUid();
-            Settings.checkAndNoteWriteSettingsOperation(context, uid, Settings
-                    .getPackageNameForUid(context, uid), true /* throwException */);
+            // If callingPkg's uid is not same as Binder.getCallingUid(),
+            // AppOpsService throws SecurityException.
+            Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPkg,
+                    true /* throwException */);
         }
     }
 
@@ -1996,7 +2006,9 @@
      */
     public int tether(String iface) {
         try {
-            return mService.tether(iface);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "tether caller:" + pkgName);
+            return mService.tether(iface, pkgName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2022,7 +2034,9 @@
      */
     public int untether(String iface) {
         try {
-            return mService.untether(iface);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "untether caller:" + pkgName);
+            return mService.untether(iface, pkgName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2099,7 +2113,7 @@
     @SystemApi
     public void startTethering(int type, boolean showProvisioningUi,
             final OnStartTetheringCallback callback, Handler handler) {
-        checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
+        Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
 
         ResultReceiver wrappedCallback = new ResultReceiver(handler) {
             @Override
@@ -2113,7 +2127,9 @@
         };
 
         try {
-            mService.startTethering(type, wrappedCallback, showProvisioningUi);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "startTethering caller:" + pkgName);
+            mService.startTethering(type, wrappedCallback, showProvisioningUi, pkgName);
         } catch (RemoteException e) {
             Log.e(TAG, "Exception trying to start tethering.", e);
             wrappedCallback.send(TETHER_ERROR_SERVICE_UNAVAIL, null);
@@ -2133,7 +2149,9 @@
     @SystemApi
     public void stopTethering(int type) {
         try {
-            mService.stopTethering(type);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "stopTethering caller:" + pkgName);
+            mService.stopTethering(type, pkgName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2218,7 +2236,9 @@
      */
     public int setUsbTethering(boolean enable) {
         try {
-            return mService.setUsbTethering(enable);
+            String pkgName = mContext.getOpPackageName();
+            Log.i(TAG, "setUsbTethering caller:" + pkgName);
+            return mService.setUsbTethering(enable, pkgName);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2559,8 +2579,16 @@
     }
 
     /**
-     * Base class for NetworkRequest callbacks.  Used for notifications about network
-     * changes.  Should be extended by applications wanting notifications.
+     * Base class for {@code NetworkRequest} callbacks. Used for notifications about network
+     * changes. Should be extended by applications wanting notifications.
+     *
+     * A {@code NetworkCallback} is registered by calling
+     * {@link #requestNetwork(NetworkRequest, NetworkCallback)},
+     * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)},
+     * or {@link #registerDefaultNetworkCallback(NetworkCallback). A {@code NetworkCallback} is
+     * unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}.
+     * A {@code NetworkCallback} should be registered at most once at any time.
+     * A {@code NetworkCallback} that has been unregistered can be registered again.
      */
     public static class NetworkCallback {
         /**
@@ -2615,7 +2643,7 @@
 
         /**
          * Called if no network is found in the timeout time specified in
-         * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)} call. This callback is not
+         * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call. This callback is not
          * called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)}
          * without timeout. When this callback is invoked the associated
          * {@link NetworkRequest} will have already been removed and released, as if
@@ -2663,6 +2691,32 @@
         public void onNetworkResumed(Network network) {}
 
         private NetworkRequest networkRequest;
+
+        private boolean isRegistered() {
+            return (networkRequest != null) && (networkRequest.requestId != REQUEST_ID_UNSET);
+        }
+    }
+
+    /**
+     * Constant error codes used by ConnectivityService to communicate about failures and errors
+     * across a Binder boundary.
+     * @hide
+     */
+    public interface Errors {
+        static int TOO_MANY_REQUESTS = 1;
+    }
+
+    /** @hide */
+    public static class TooManyRequestsException extends RuntimeException {}
+
+    private static RuntimeException convertServiceException(ServiceSpecificException e) {
+        switch (e.errorCode) {
+            case Errors.TOO_MANY_REQUESTS:
+                return new TooManyRequestsException();
+            default:
+                Log.w(TAG, "Unknown service error code " + e.errorCode);
+                return new RuntimeException(e);
+        }
     }
 
     private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
@@ -2680,17 +2734,12 @@
     public static final int CALLBACK_CAP_CHANGED         = BASE + 6;
     /** @hide */
     public static final int CALLBACK_IP_CHANGED          = BASE + 7;
-    /** @hide */
-    public static final int CALLBACK_RELEASED            = BASE + 8;
-    // TODO: consider deleting CALLBACK_EXIT and shifting following enum codes down by 1.
-    /** @hide */
-    public static final int CALLBACK_EXIT                = BASE + 9;
     /** @hide obj = NetworkCapabilities, arg1 = seq number */
-    private static final int EXPIRE_LEGACY_REQUEST       = BASE + 10;
+    private static final int EXPIRE_LEGACY_REQUEST       = BASE + 8;
     /** @hide */
-    public static final int CALLBACK_SUSPENDED           = BASE + 11;
+    public static final int CALLBACK_SUSPENDED           = BASE + 9;
     /** @hide */
-    public static final int CALLBACK_RESUMED             = BASE + 12;
+    public static final int CALLBACK_RESUMED             = BASE + 10;
 
     /** @hide */
     public static String getCallbackName(int whichCallback) {
@@ -2702,8 +2751,6 @@
             case CALLBACK_UNAVAIL:      return "CALLBACK_UNAVAIL";
             case CALLBACK_CAP_CHANGED:  return "CALLBACK_CAP_CHANGED";
             case CALLBACK_IP_CHANGED:   return "CALLBACK_IP_CHANGED";
-            case CALLBACK_RELEASED:     return "CALLBACK_RELEASED";
-            case CALLBACK_EXIT:         return "CALLBACK_EXIT";
             case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST";
             case CALLBACK_SUSPENDED:    return "CALLBACK_SUSPENDED";
             case CALLBACK_RESUMED:      return "CALLBACK_RESUMED";
@@ -2721,97 +2768,67 @@
         }
 
         CallbackHandler(Handler handler) {
-            this(handler.getLooper());
+            this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper());
         }
 
         @Override
         public void handleMessage(Message message) {
-            NetworkRequest request = getObject(message, NetworkRequest.class);
-            Network network = getObject(message, Network.class);
-            if (DBG) {
-                Log.d(TAG, whatToString(message.what) + " for network " + network);
+            if (message.what == EXPIRE_LEGACY_REQUEST) {
+                expireRequest((NetworkCapabilities) message.obj, message.arg1);
+                return;
             }
+
+            final NetworkRequest request = getObject(message, NetworkRequest.class);
+            final Network network = getObject(message, Network.class);
+            final NetworkCallback callback;
+            synchronized (sCallbacks) {
+                callback = sCallbacks.get(request);
+            }
+            if (DBG) {
+                Log.d(TAG, getCallbackName(message.what) + " for network " + network);
+            }
+            if (callback == null) {
+                Log.w(TAG, "callback not found for " + getCallbackName(message.what) + " message");
+                return;
+            }
+
             switch (message.what) {
                 case CALLBACK_PRECHECK: {
-                    NetworkCallback callback = getCallback(request, "PRECHECK");
-                    if (callback != null) {
-                        callback.onPreCheck(network);
-                    }
+                    callback.onPreCheck(network);
                     break;
                 }
                 case CALLBACK_AVAILABLE: {
-                    NetworkCallback callback = getCallback(request, "AVAILABLE");
-                    if (callback != null) {
-                        callback.onAvailable(network);
-                    }
+                    callback.onAvailable(network);
                     break;
                 }
                 case CALLBACK_LOSING: {
-                    NetworkCallback callback = getCallback(request, "LOSING");
-                    if (callback != null) {
-                        callback.onLosing(network, message.arg1);
-                    }
+                    callback.onLosing(network, message.arg1);
                     break;
                 }
                 case CALLBACK_LOST: {
-                    NetworkCallback callback = getCallback(request, "LOST");
-                    if (callback != null) {
-                        callback.onLost(network);
-                    }
+                    callback.onLost(network);
                     break;
                 }
                 case CALLBACK_UNAVAIL: {
-                    NetworkCallback callback = getCallback(request, "UNAVAIL");
-                    if (callback != null) {
-                        callback.onUnavailable();
-                    }
+                    callback.onUnavailable();
                     break;
                 }
                 case CALLBACK_CAP_CHANGED: {
-                    NetworkCallback callback = getCallback(request, "CAP_CHANGED");
-                    if (callback != null) {
-                        NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
-                        callback.onCapabilitiesChanged(network, cap);
-                    }
+                    NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
+                    callback.onCapabilitiesChanged(network, cap);
                     break;
                 }
                 case CALLBACK_IP_CHANGED: {
-                    NetworkCallback callback = getCallback(request, "IP_CHANGED");
-                    if (callback != null) {
-                        LinkProperties lp = getObject(message, LinkProperties.class);
-                        callback.onLinkPropertiesChanged(network, lp);
-                    }
+                    LinkProperties lp = getObject(message, LinkProperties.class);
+                    callback.onLinkPropertiesChanged(network, lp);
                     break;
                 }
                 case CALLBACK_SUSPENDED: {
-                    NetworkCallback callback = getCallback(request, "SUSPENDED");
-                    if (callback != null) {
-                        callback.onNetworkSuspended(network);
-                    }
+                    callback.onNetworkSuspended(network);
                     break;
                 }
                 case CALLBACK_RESUMED: {
-                    NetworkCallback callback = getCallback(request, "RESUMED");
-                    if (callback != null) {
-                        callback.onNetworkResumed(network);
-                    }
-                    break;
-                }
-                case CALLBACK_RELEASED: {
-                    final NetworkCallback callback;
-                    synchronized(sCallbacks) {
-                        callback = sCallbacks.remove(request);
-                    }
-                    if (callback == null) {
-                        Log.e(TAG, "callback not found for RELEASED message");
-                    }
-                    break;
-                }
-                case CALLBACK_EXIT: {
-                    break;
-                }
-                case EXPIRE_LEGACY_REQUEST: {
-                    expireRequest((NetworkCapabilities)message.obj, message.arg1);
+                    callback.onNetworkResumed(network);
                     break;
                 }
             }
@@ -2820,17 +2837,6 @@
         private <T> T getObject(Message msg, Class<T> c) {
             return (T) msg.getData().getParcelable(c.getSimpleName());
         }
-
-        private NetworkCallback getCallback(NetworkRequest req, String name) {
-            NetworkCallback callback;
-            synchronized(sCallbacks) {
-                callback = sCallbacks.get(req);
-            }
-            if (callback == null) {
-                Log.e(TAG, "callback not found for " + name + " message");
-            }
-            return callback;
-        }
     }
 
     private CallbackHandler getDefaultHandler() {
@@ -2850,17 +2856,16 @@
 
     private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
             int timeoutMs, int action, int legacyType, CallbackHandler handler) {
-        if (callback == null) {
-            throw new IllegalArgumentException("null NetworkCallback");
-        }
-        if (need == null && action != REQUEST) {
-            throw new IllegalArgumentException("null NetworkCapabilities");
-        }
-        // TODO: throw an exception if callback.networkRequest is not null.
-        // http://b/20701525
+        checkCallbackNotNull(callback);
+        Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities");
         final NetworkRequest request;
         try {
             synchronized(sCallbacks) {
+                if (callback.isRegistered()) {
+                    // TODO: throw exception instead and enforce 1:1 mapping of callbacks
+                    // and requests (http://b/20701525).
+                    Log.e(TAG, "NetworkCallback was already registered");
+                }
                 Messenger messenger = new Messenger(handler);
                 Binder binder = new Binder();
                 if (action == LISTEN) {
@@ -2876,6 +2881,8 @@
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw convertServiceException(e);
         }
         return request;
     }
@@ -2904,7 +2911,7 @@
      * This {@link NetworkRequest} will live until released via
      * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
      * version of the method which takes a timeout is
-     * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
+     * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
      * Status of the request can be followed by listening to the various
      * callbacks described in {@link NetworkCallback}.  The {@link Network}
      * can be used to direct traffic to the network.
@@ -2939,7 +2946,7 @@
      * This {@link NetworkRequest} will live until released via
      * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
      * version of the method which takes a timeout is
-     * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
+     * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
      * Status of the request can be followed by listening to the various
      * callbacks described in {@link NetworkCallback}.  The {@link Network}
      * can be used to direct traffic to the network.
@@ -2972,50 +2979,6 @@
     }
 
     /**
-     * Note: this is a deprecated version of
-     * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)} - please transition code to use
-     * the unhidden version of the function.
-     * TODO: replace all callers with the new version of the API
-     *
-     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
-     * by a timeout.
-     *
-     * This function behaves identically to the non-timed-out version
-     * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, but if a suitable network
-     * is not found within the given time (in milliseconds) the
-     * {@link NetworkCallback#onUnavailable()} callback is called. The request can still be
-     * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does
-     * not have to be released if timed-out (it is automatically released). Unregistering a
-     * request that timed out is not an error.
-     *
-     * <p>Do not use this method to poll for the existence of specific networks (e.g. with a small
-     * timeout) - the {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided
-     * for that purpose. Calling this method will attempt to bring up the requested network.
-     *
-     * <p>This method requires the caller to hold either the
-     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
-     * or the ability to modify system settings as determined by
-     * {@link android.provider.Settings.System#canWrite}.</p>
-     *
-     * @param request {@link NetworkRequest} describing this request.
-     * @param networkCallback The callbacks to be utilized for this request.  Note
-     *                        the callbacks must not be shared - they uniquely specify
-     *                        this request.
-     * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
-     *                  before {@link NetworkCallback#onUnavailable()} is called. The timeout must
-     *                  be a positive value (i.e. >0).
-     * @hide
-     */
-    public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
-            int timeoutMs) {
-        if (timeoutMs <= 0) {
-            throw new IllegalArgumentException("Non-positive timeoutMs: " + timeoutMs);
-        }
-        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
-        requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
-    }
-
-    /**
      * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
      * by a timeout.
      *
@@ -3037,22 +3000,19 @@
      * {@link android.provider.Settings.System#canWrite}.</p>
      *
      * @param request {@link NetworkRequest} describing this request.
+     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+     *                        the callback must not be shared - it uniquely specifies this request.
      * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
      *                  before {@link NetworkCallback#onUnavailable()} is called. The timeout must
      *                  be a positive value (i.e. >0).
-     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
-     *                        the callback must not be shared - it uniquely specifies this request.
      */
-    public void requestNetwork(NetworkRequest request, int timeoutMs,
-            NetworkCallback networkCallback) {
-        if (timeoutMs <= 0) {
-            throw new IllegalArgumentException("Non-positive timeoutMs: " + timeoutMs);
-        }
+    public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
+            int timeoutMs) {
+        checkTimeout(timeoutMs);
         int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
         requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
     }
 
-
     /**
      * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
      * by a timeout.
@@ -3074,17 +3034,15 @@
      * {@link android.provider.Settings.System#canWrite}.</p>
      *
      * @param request {@link NetworkRequest} describing this request.
-     * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
-     *                  before {@link NetworkCallback#onUnavailable} is called.
      * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
      *                        the callback must not be shared - it uniquely specifies this request.
      * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+     *                  before {@link NetworkCallback#onUnavailable} is called.
      */
-    public void requestNetwork(NetworkRequest request, int timeoutMs,
-            NetworkCallback networkCallback, Handler handler) {
-        if (timeoutMs <= 0) {
-            throw new IllegalArgumentException("Non-positive timeoutMs");
-        }
+    public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
+            Handler handler, int timeoutMs) {
+        checkTimeout(timeoutMs);
         int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
         CallbackHandler cbHandler = new CallbackHandler(handler);
         requestNetwork(request, networkCallback, timeoutMs, legacyType, cbHandler);
@@ -3156,11 +3114,13 @@
      *         {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}.
      */
     public void requestNetwork(NetworkRequest request, PendingIntent operation) {
-        checkPendingIntent(operation);
+        checkPendingIntentNotNull(operation);
         try {
             mService.pendingRequestForNetwork(request.networkCapabilities, operation);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw convertServiceException(e);
         }
     }
 
@@ -3177,7 +3137,7 @@
      *                  corresponding NetworkRequest you'd like to remove. Cannot be null.
      */
     public void releaseNetworkRequest(PendingIntent operation) {
-        checkPendingIntent(operation);
+        checkPendingIntentNotNull(operation);
         try {
             mService.releasePendingNetworkRequest(operation);
         } catch (RemoteException e) {
@@ -3185,10 +3145,16 @@
         }
     }
 
-    private void checkPendingIntent(PendingIntent intent) {
-        if (intent == null) {
-            throw new IllegalArgumentException("PendingIntent cannot be null.");
-        }
+    private static void checkPendingIntentNotNull(PendingIntent intent) {
+        Preconditions.checkNotNull(intent, "PendingIntent cannot be null.");
+    }
+
+    private static void checkCallbackNotNull(NetworkCallback callback) {
+        Preconditions.checkNotNull(callback, "null NetworkCallback");
+    }
+
+    private static void checkTimeout(int timeoutMs) {
+        Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive.");
     }
 
     /**
@@ -3258,11 +3224,13 @@
      *                  comes from {@link PendingIntent#getBroadcast}. Cannot be null.
      */
     public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) {
-        checkPendingIntent(operation);
+        checkPendingIntentNotNull(operation);
         try {
             mService.pendingListenForNetwork(request.networkCapabilities, operation);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw convertServiceException(e);
         }
     }
 
@@ -3300,8 +3268,9 @@
         // capabilities, this request is guaranteed, at all times, to be
         // satisfied by the same network, if any, that satisfies the default
         // request, i.e., the system default network.
+        NetworkCapabilities nullCapabilities = null;
         CallbackHandler cbHandler = new CallbackHandler(handler);
-        sendRequestForNetwork(null, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
+        sendRequestForNetwork(nullCapabilities, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
     }
 
     /**
@@ -3325,25 +3294,42 @@
     }
 
     /**
-     * Unregisters callbacks about and possibly releases networks originating from
+     * Unregisters a {@code NetworkCallback} and possibly releases networks originating from
      * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and
      * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} calls.
      * If the given {@code NetworkCallback} had previously been used with
      * {@code #requestNetwork}, any networks that had been connected to only to satisfy that request
      * will be disconnected.
      *
+     * Notifications that would have triggered that {@code NetworkCallback} will immediately stop
+     * triggering it as soon as this call returns.
+     *
      * @param networkCallback The {@link NetworkCallback} used when making the request.
      */
     public void unregisterNetworkCallback(NetworkCallback networkCallback) {
-        if (networkCallback == null || networkCallback.networkRequest == null ||
-                networkCallback.networkRequest.requestId == REQUEST_ID_UNSET) {
-            throw new IllegalArgumentException("Invalid NetworkCallback");
-        }
-        try {
-            // CallbackHandler will release callback when receiving CALLBACK_RELEASED.
-            mService.releaseNetworkRequest(networkCallback.networkRequest);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        checkCallbackNotNull(networkCallback);
+        final List<NetworkRequest> reqs = new ArrayList<>();
+        // Find all requests associated to this callback and stop callback triggers immediately.
+        // Callback is reusable immediately. http://b/20701525, http://b/35921499.
+        synchronized (sCallbacks) {
+            Preconditions.checkArgument(
+                    networkCallback.isRegistered(), "NetworkCallback was not registered");
+            for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) {
+                if (e.getValue() == networkCallback) {
+                    reqs.add(e.getKey());
+                }
+            }
+            // TODO: throw exception if callback was registered more than once (http://b/20701525).
+            for (NetworkRequest r : reqs) {
+                try {
+                    mService.releaseNetworkRequest(r);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                // Only remove mapping if rpc was successful.
+                sCallbacks.remove(r);
+            }
+            networkCallback.networkRequest = null;
         }
     }
 
@@ -3357,6 +3343,7 @@
      *                  Cannot be null.
      */
     public void unregisterNetworkCallback(PendingIntent operation) {
+        checkPendingIntentNotNull(operation);
         releaseNetworkRequest(operation);
     }
 
@@ -3407,10 +3394,26 @@
     }
 
     /**
+     * Requests that the system open the captive portal app on the specified network.
+     *
+     * @param network The network to log into.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+    public void startCaptivePortalApp(Network network) {
+        try {
+            mService.startCaptivePortalApp(network);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * It is acceptable to briefly use multipath data to provide seamless connectivity for
      * time-sensitive user-facing operations when the system default network is temporarily
-     * unresponsive. The amount of data should be limited (less than one megabyte), and the
-     * operation should be infrequent to ensure that data usage is limited.
+     * unresponsive. The amount of data should be limited (less than one megabyte for every call to
+     * this method), and the operation should be infrequent to ensure that data usage is limited.
      *
      * An example of such an operation might be a time-sensitive foreground activity, such as a
      * voice command, that the user is performing while walking out of range of a Wi-Fi network.
@@ -3698,32 +3701,4 @@
             throw e.rethrowFromSystemServer();
         }
     }
-
-    /**
-     * A holder class for debug info (mapping CALLBACK values to field names). This is stored
-     * in a holder for two reasons:
-     * 1) The reflection necessary to establish the map can't be run at compile-time. Thus, this
-     *    code will make the enclosing class not compile-time initializeable, deferring its
-     *    initialization to zygote startup. This leads to dirty (but shared) memory.
-     *    As this is debug info, use a holder that isn't initialized by default. This way the map
-     *    will be created on demand, while ConnectivityManager can be compile-time initialized.
-     * 2) Static initialization is still preferred for its strong thread safety guarantees without
-     *    requiring a lock.
-     */
-    private static class NoPreloadHolder {
-        public static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
-                new Class[]{ConnectivityManager.class}, new String[]{"CALLBACK_"});
-    }
-
-    static {
-        // When debug is enabled, aggressively initialize the holder by touching the field (which
-        // will guarantee static initialization).
-        if (CallbackHandler.DBG) {
-            Object dummy = NoPreloadHolder.sMagicDecoderRing;
-        }
-    }
-
-    private static final String whatToString(int what) {
-        return NoPreloadHolder.sMagicDecoderRing.get(what, Integer.toString(what));
-    }
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 425e494..27729dc 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -69,17 +69,18 @@
 
     boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
 
-    int tether(String iface);
+    int tether(String iface, String callerPkg);
 
-    int untether(String iface);
+    int untether(String iface, String callerPkg);
 
     int getLastTetherError(String iface);
 
     boolean isTetheringSupported();
 
-    void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi);
+    void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi,
+            String callerPkg);
 
-    void stopTethering(int type);
+    void stopTethering(int type, String callerPkg);
 
     String[] getTetherableIfaces();
 
@@ -95,7 +96,7 @@
 
     String[] getTetherableBluetoothRegexs();
 
-    int setUsbTethering(boolean enable);
+    int setUsbTethering(boolean enable, String callerPkg);
 
     void reportInetCondition(int networkType, int percentage);
 
@@ -160,6 +161,7 @@
 
     void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
     void setAvoidUnvalidated(in Network network);
+    void startCaptivePortalApp(in Network network);
 
     int getMultipathPreference(in Network Network);
 
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 7fea4a2..48b095d 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -24,6 +24,8 @@
 /**
  * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
  * RFC 4301.
+ *
+ * @hide
  */
 public final class IpSecAlgorithm implements Parcelable {
 
@@ -32,7 +34,7 @@
      *
      * <p>Valid lengths for this key are {128, 192, 256}.
      */
-    public static final String ALGO_CRYPT_AES_CBC = "cbc(aes)";
+    public static final String CRYPT_AES_CBC = "cbc(aes)";
 
     /**
      * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new
@@ -40,7 +42,7 @@
      *
      * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128.
      */
-    public static final String ALGO_AUTH_HMAC_MD5 = "hmac(md5)";
+    public static final String AUTH_HMAC_MD5 = "hmac(md5)";
 
     /**
      * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in
@@ -48,35 +50,35 @@
      *
      * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160.
      */
-    public static final String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)";
+    public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
 
     /**
      * SHA256 HMAC Authentication/Integrity Algorithm.
      *
      * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 256.
      */
-    public static final String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)";
+    public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
 
     /**
      * SHA384 HMAC Authentication/Integrity Algorithm.
      *
      * <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384.
      */
-    public static final String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)";
+    public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
     /**
      * SHA512 HMAC Authentication/Integrity Algorithm
      *
      * <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512.
      */
-    public static final String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)";
+    public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
 
     /** @hide */
     @StringDef({
-        ALGO_CRYPT_AES_CBC,
-        ALGO_AUTH_HMAC_MD5,
-        ALGO_AUTH_HMAC_SHA1,
-        ALGO_AUTH_HMAC_SHA256,
-        ALGO_AUTH_HMAC_SHA512
+        CRYPT_AES_CBC,
+        AUTH_HMAC_MD5,
+        AUTH_HMAC_SHA1,
+        AUTH_HMAC_SHA256,
+        AUTH_HMAC_SHA512
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AlgorithmName {}
@@ -164,17 +166,17 @@
 
     private static boolean isTruncationLengthValid(String algo, int truncLenBits) {
         switch (algo) {
-            case ALGO_CRYPT_AES_CBC:
+            case CRYPT_AES_CBC:
                 return (truncLenBits == 128 || truncLenBits == 192 || truncLenBits == 256);
-            case ALGO_AUTH_HMAC_MD5:
+            case AUTH_HMAC_MD5:
                 return (truncLenBits >= 96 && truncLenBits <= 128);
-            case ALGO_AUTH_HMAC_SHA1:
+            case AUTH_HMAC_SHA1:
                 return (truncLenBits >= 96 && truncLenBits <= 160);
-            case ALGO_AUTH_HMAC_SHA256:
+            case AUTH_HMAC_SHA256:
                 return (truncLenBits >= 96 && truncLenBits <= 256);
-            case ALGO_AUTH_HMAC_SHA384:
+            case AUTH_HMAC_SHA384:
                 return (truncLenBits >= 192 && truncLenBits <= 384);
-            case ALGO_AUTH_HMAC_SHA512:
+            case AUTH_HMAC_SHA512:
                 return (truncLenBits >= 256 && truncLenBits <= 512);
             default:
                 return false;
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 6852beb..114e46e 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -37,6 +37,8 @@
  * <p>An IpSecManager may be obtained by calling {@link
  * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
  * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
+ *
+ * @hide
  */
 public final class IpSecManager {
     private static final String TAG = "IpSecManager";
@@ -193,15 +195,44 @@
      *
      * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
      * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
-     * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
      * @return the reserved SecurityParameterIndex
      * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
      *     for this user
      * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
      */
     public SecurityParameterIndex reserveSecurityParameterIndex(
+            int direction, InetAddress remoteAddress)
+            throws ResourceUnavailableException {
+        try {
+            return new SecurityParameterIndex(
+                    mService,
+                    direction,
+                    remoteAddress,
+                    IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
+        } catch (SpiUnavailableException unlikely) {
+            throw new ResourceUnavailableException("No SPIs available");
+        }
+    }
+
+    /**
+     * Reserve an SPI for traffic bound towards the specified remote address.
+     *
+     * <p>If successful, this SPI is guaranteed available until released by a call to {@link
+     * SecurityParameterIndex#close()}.
+     *
+     * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
+     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
+     * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
+     * @return the reserved SecurityParameterIndex
+     * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
+     *     for this user
+     */
+    public SecurityParameterIndex reserveSecurityParameterIndex(
             int direction, InetAddress remoteAddress, int requestedSpi)
             throws SpiUnavailableException, ResourceUnavailableException {
+        if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) {
+            throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI");
+        }
         return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi);
     }
 
@@ -216,6 +247,7 @@
      *
      * @param socket a stream socket
      * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+     * @hide
      */
     public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
             throws IOException {
@@ -233,6 +265,7 @@
      *
      * @param socket a datagram socket
      * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+     * @hide
      */
     public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
             throws IOException {
@@ -249,6 +282,23 @@
     }
 
     /**
+     * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
+     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
+     * transform. For security reasons, attempts to send traffic to any IP address other than the
+     * address associated with that transform will throw an IOException. In addition, if the
+     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
+     * send() or receive() until the transform is removed from the socket by calling {@link
+     * #removeTransportModeTransform(FileDescriptor, IpSecTransform)};
+     *
+     * @param socket a socket file descriptor
+     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+     */
+    public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
+            throws IOException {
+        applyTransportModeTransform(new ParcelFileDescriptor(socket), transform);
+    }
+
+    /**
      * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to
      * and from that network's interface with IPsec (applies an outer IP header and IPsec Header to
      * all traffic, and expects an additional IP header and IPsec Header on all inbound traffic).
@@ -270,8 +320,10 @@
      *
      * @param socket a socket that previously had a transform applied to it.
      * @param transform the IPsec Transform that was previously applied to the given socket
+     * @hide
      */
-    public void removeTransportModeTransform(Socket socket, IpSecTransform transform) {
+    public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
+            throws IOException {
         removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
     }
 
@@ -284,11 +336,28 @@
      *
      * @param socket a socket that previously had a transform applied to it.
      * @param transform the IPsec Transform that was previously applied to the given socket
+     * @hide
      */
-    public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) {
+    public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
+            throws IOException {
         removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
     }
 
+    /**
+     * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
+     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
+     * communication in the clear in the event socket reuse is desired. This operation will succeed
+     * regardless of the underlying state of a transform. If a transform is removed, communication
+     * on all sockets to which that transform was applied will fail until this method is called.
+     *
+     * @param socket a socket file descriptor that previously had a transform applied to it.
+     * @param transform the IPsec Transform that was previously applied to the given socket
+     */
+    public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
+            throws IOException {
+        removeTransportModeTransform(new ParcelFileDescriptor(socket), transform);
+    }
+
     /* Call down to activate a transform */
     private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
         try {
@@ -359,7 +428,7 @@
          *
          * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
          */
-        public void close() {
+        public void close() throws IOException {
             // TODO: Go close the socket
             mCloseGuard.close();
         }
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 801e98c..639d1f2 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -45,6 +45,8 @@
  *
  * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
  * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
+ *
+ * @hide
  */
 public final class IpSecTransform implements AutoCloseable {
     private static final String TAG = "IpSecTransform";
diff --git a/core/java/android/net/MatchAllNetworkSpecifier.java b/core/java/android/net/MatchAllNetworkSpecifier.java
new file mode 100644
index 0000000..7aafc93
--- /dev/null
+++ b/core/java/android/net/MatchAllNetworkSpecifier.java
@@ -0,0 +1,80 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * MatchAllNetworkSpecifier is a marker class used by NetworkFactory classes to indicate
+ * that they accept (match) any network specifier in requests.
+ *
+ * The class must never be used as part of a network request (those semantics aren't specified).
+ *
+ * @hide
+ */
+public final class MatchAllNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+    /**
+     * Utility method which verifies that the ns argument is not a MatchAllNetworkSpecifier and
+     * throws an IllegalArgumentException if it is.
+     */
+    public static void checkNotMatchAllNetworkSpecifier(NetworkSpecifier ns) {
+        if (ns instanceof MatchAllNetworkSpecifier) {
+            throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted");
+        }
+    }
+
+    public boolean satisfiedBy(NetworkSpecifier other) {
+        /*
+         * The method is called by a NetworkRequest to see if it is satisfied by a proposed
+         * network (e.g. as offered by a network factory). Since MatchAllNetweorkSpecifier must
+         * not be used in network requests this method should never be called.
+         */
+        throw new IllegalStateException(
+                "MatchAllNetworkSpecifier must not be used in NetworkRequests");
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof MatchAllNetworkSpecifier;
+    }
+
+    @Override
+    public int hashCode() {
+        return 0;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        // Nothing to write.
+    }
+
+    public static final Parcelable.Creator<MatchAllNetworkSpecifier> CREATOR =
+            new Parcelable.Creator<MatchAllNetworkSpecifier>() {
+        public MatchAllNetworkSpecifier createFromParcel(Parcel in) {
+            return new MatchAllNetworkSpecifier();
+        }
+        public MatchAllNetworkSpecifier[] newArray(int size) {
+            return new MatchAllNetworkSpecifier[size];
+        }
+    };
+}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 4dd8ce9..1da0d28 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -18,8 +18,11 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.text.TextUtils;
-import java.lang.IllegalArgumentException;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.BitUtils;
+
+import java.util.Objects;
 
 /**
  * This class represents the capabilities of a network.  This is used both to specify
@@ -33,6 +36,8 @@
  * all cellular based connections are metered and all Wi-Fi based connections are not.
  */
 public final class NetworkCapabilities implements Parcelable {
+    private static final String TAG = "NetworkCapabilities";
+
     /**
      * @hide
      */
@@ -205,19 +210,6 @@
             (1 << NET_CAPABILITY_FOREGROUND);
 
     /**
-     * Network specifier for factories which want to match any network specifier
-     * (NS) in a request. Behavior:
-     * <li>Empty NS in request matches any network factory NS</li>
-     * <li>Empty NS in the network factory NS only matches a request with an
-     * empty NS</li>
-     * <li>"*" (this constant) NS in the network factory matches requests with
-     * any NS</li>
-     *
-     * @hide
-     */
-    public static final String MATCH_ALL_REQUESTS_NETWORK_SPECIFIER = "*";
-
-    /**
      * Network capabilities that are not allowed in NetworkRequests. This exists because the
      * NetworkFactory / NetworkAgent model does not deal well with the situation where a
      * capability's presence cannot be known in advance. If such a capability is requested, then we
@@ -239,7 +231,8 @@
      * Capabilities that suggest that a network is restricted.
      * {@see #maybeMarkCapabilitiesRestricted}.
      */
-    private static final long RESTRICTED_CAPABILITIES =
+    @VisibleForTesting
+    /* package */ static final long RESTRICTED_CAPABILITIES =
             (1 << NET_CAPABILITY_CBS) |
             (1 << NET_CAPABILITY_DUN) |
             (1 << NET_CAPABILITY_EIMS) |
@@ -250,6 +243,17 @@
             (1 << NET_CAPABILITY_XCAP);
 
     /**
+     * Capabilities that suggest that a network is unrestricted.
+     * {@see #maybeMarkCapabilitiesRestricted}.
+     */
+    @VisibleForTesting
+    /* package */ static final long UNRESTRICTED_CAPABILITIES =
+            (1 << NET_CAPABILITY_INTERNET) |
+            (1 << NET_CAPABILITY_MMS) |
+            (1 << NET_CAPABILITY_SUPL) |
+            (1 << NET_CAPABILITY_WIFI_P2P);
+
+    /**
      * Adds the given capability to this {@code NetworkCapability} instance.
      * Multiple capabilities may be applied sequentially.  Note that when searching
      * for a network to satisfy a request, all capabilities requested must be satisfied.
@@ -289,7 +293,7 @@
      * @hide
      */
     public int[] getCapabilities() {
-        return enumerateBits(mNetworkCapabilities);
+        return BitUtils.unpackBits(mNetworkCapabilities);
     }
 
     /**
@@ -305,19 +309,6 @@
         return ((mNetworkCapabilities & (1 << capability)) != 0);
     }
 
-    private int[] enumerateBits(long val) {
-        int size = Long.bitCount(val);
-        int[] result = new int[size];
-        int index = 0;
-        int resource = 0;
-        while (val > 0) {
-            if ((val & 1) == 1) result[index++] = resource;
-            val = val >> 1;
-            resource++;
-        }
-        return result;
-    }
-
     private void combineNetCapabilities(NetworkCapabilities nc) {
         this.mNetworkCapabilities |= nc.mNetworkCapabilities;
     }
@@ -375,12 +366,16 @@
      * @hide
      */
     public void maybeMarkCapabilitiesRestricted() {
-        // If all the capabilities are typically provided by restricted networks, conclude that this
-        // network is restricted.
-        if ((mNetworkCapabilities & ~(DEFAULT_CAPABILITIES | RESTRICTED_CAPABILITIES)) == 0 &&
-                // Must have at least some restricted capabilities, otherwise a request for an
-                // internet-less network will get marked restricted.
-                (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0) {
+        // Verify there aren't any unrestricted capabilities.  If there are we say
+        // the whole thing is unrestricted.
+        final boolean hasUnrestrictedCapabilities =
+                ((mNetworkCapabilities & UNRESTRICTED_CAPABILITIES) != 0);
+
+        // Must have at least some restricted capabilities.
+        final boolean hasRestrictedCapabilities =
+                ((mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0);
+
+        if (hasRestrictedCapabilities && !hasUnrestrictedCapabilities) {
             removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
         }
     }
@@ -426,6 +421,15 @@
     private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
     private static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
 
+    private static final String[] TRANSPORT_NAMES = {
+        "CELLULAR",
+        "WIFI",
+        "BLUETOOTH",
+        "ETHERNET",
+        "VPN",
+        "WIFI_AWARE"
+    };
+
     /**
      * Adds the given transport type to this {@code NetworkCapability} instance.
      * Multiple transports may be applied sequentially.  Note that when searching
@@ -472,7 +476,7 @@
      * @hide
      */
     public int[] getTransportTypes() {
-        return enumerateBits(mTransportTypes);
+        return BitUtils.unpackBits(mTransportTypes);
     }
 
     /**
@@ -581,63 +585,56 @@
                 this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps);
     }
 
-    private String mNetworkSpecifier;
+    private NetworkSpecifier mNetworkSpecifier = null;
+
     /**
      * Sets the optional bearer specific network specifier.
      * This has no meaning if a single transport is also not specified, so calling
      * this without a single transport set will generate an exception, as will
      * subsequently adding or removing transports after this is set.
      * </p>
-     * The interpretation of this {@code String} is bearer specific and bearers that use
-     * it should document their particulars.  For example, Bluetooth may use some sort of
-     * device id while WiFi could used SSID and/or BSSID.  Cellular may use carrier SPN (name)
-     * or Subscription ID.
      *
-     * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
-     *                         specific network specifier where the bearer has a choice of
-     *                         networks.
+     * @param networkSpecifier A concrete, parcelable framework class that extends
+     *                         NetworkSpecifier.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities setNetworkSpecifier(String networkSpecifier) {
-        if (TextUtils.isEmpty(networkSpecifier) == false && Long.bitCount(mTransportTypes) != 1) {
+    public NetworkCapabilities setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+        if (networkSpecifier != null && Long.bitCount(mTransportTypes) != 1) {
             throw new IllegalStateException("Must have a single transport specified to use " +
                     "setNetworkSpecifier");
         }
+
         mNetworkSpecifier = networkSpecifier;
+
         return this;
     }
 
     /**
      * Gets the optional bearer specific network specifier.
      *
-     * @return The optional {@code String} specifying the bearer specific network specifier.
-     *         See {@link #setNetworkSpecifier}.
+     * @return The optional {@link NetworkSpecifier} specifying the bearer specific network
+     *         specifier. See {@link #setNetworkSpecifier}.
      * @hide
      */
-    public String getNetworkSpecifier() {
+    public NetworkSpecifier getNetworkSpecifier() {
         return mNetworkSpecifier;
     }
 
     private void combineSpecifiers(NetworkCapabilities nc) {
-        String otherSpecifier = nc.getNetworkSpecifier();
-        if (TextUtils.isEmpty(otherSpecifier)) return;
-        if (TextUtils.isEmpty(mNetworkSpecifier) == false) {
+        if (mNetworkSpecifier != null && !mNetworkSpecifier.equals(nc.mNetworkSpecifier)) {
             throw new IllegalStateException("Can't combine two networkSpecifiers");
         }
-        setNetworkSpecifier(otherSpecifier);
+        setNetworkSpecifier(nc.mNetworkSpecifier);
     }
+
     private boolean satisfiedBySpecifier(NetworkCapabilities nc) {
-        return (TextUtils.isEmpty(mNetworkSpecifier) ||
-                mNetworkSpecifier.equals(nc.mNetworkSpecifier) ||
-                MATCH_ALL_REQUESTS_NETWORK_SPECIFIER.equals(nc.mNetworkSpecifier));
+        return mNetworkSpecifier == null || mNetworkSpecifier.satisfiedBy(nc.mNetworkSpecifier)
+                || nc.mNetworkSpecifier instanceof MatchAllNetworkSpecifier;
     }
+
     private boolean equalsSpecifier(NetworkCapabilities nc) {
-        if (TextUtils.isEmpty(mNetworkSpecifier)) {
-            return TextUtils.isEmpty(nc.mNetworkSpecifier);
-        } else {
-            return mNetworkSpecifier.equals(nc.mNetworkSpecifier);
-        }
+        return Objects.equals(mNetworkSpecifier, nc.mNetworkSpecifier);
     }
 
     /**
@@ -799,7 +796,7 @@
                 ((int)(mTransportTypes >> 32) * 7) +
                 (mLinkUpBandwidthKbps * 11) +
                 (mLinkDownBandwidthKbps * 13) +
-                (TextUtils.isEmpty(mNetworkSpecifier) ? 0 : mNetworkSpecifier.hashCode() * 17) +
+                Objects.hashCode(mNetworkSpecifier) * 17 +
                 (mSignalStrength * 19));
     }
 
@@ -813,7 +810,7 @@
         dest.writeLong(mTransportTypes);
         dest.writeInt(mLinkUpBandwidthKbps);
         dest.writeInt(mLinkDownBandwidthKbps);
-        dest.writeString(mNetworkSpecifier);
+        dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
         dest.writeInt(mSignalStrength);
     }
 
@@ -827,7 +824,7 @@
                 netCap.mTransportTypes = in.readLong();
                 netCap.mLinkUpBandwidthKbps = in.readInt();
                 netCap.mLinkDownBandwidthKbps = in.readInt();
-                netCap.mNetworkSpecifier = in.readString();
+                netCap.mNetworkSpecifier = in.readParcelable(null);
                 netCap.mSignalStrength = in.readInt();
                 return netCap;
             }
@@ -886,18 +883,23 @@
      * @hide
      */
     public static String transportNamesOf(int[] types) {
-        String transports = "";
-        for (int i = 0; i < types.length;) {
-            switch (types[i]) {
-                case TRANSPORT_CELLULAR:    transports += "CELLULAR"; break;
-                case TRANSPORT_WIFI:        transports += "WIFI"; break;
-                case TRANSPORT_BLUETOOTH:   transports += "BLUETOOTH"; break;
-                case TRANSPORT_ETHERNET:    transports += "ETHERNET"; break;
-                case TRANSPORT_VPN:         transports += "VPN"; break;
-                case TRANSPORT_WIFI_AWARE:  transports += "WIFI_AWARE"; break;
-            }
-            if (++i < types.length) transports += "|";
+        if (types == null || types.length == 0) {
+            return "";
         }
-        return transports;
+        StringBuilder transports = new StringBuilder();
+        for (int t : types) {
+            transports.append("|").append(transportNameOf(t));
+        }
+        return transports.substring(1);
+    }
+
+    /**
+     * @hide
+     */
+    public static String transportNameOf(int transport) {
+        if (transport < 0 || TRANSPORT_NAMES.length <= transport) {
+            return "UNKNOWN";
+        }
+        return TRANSPORT_NAMES[transport];
     }
 }
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index cb78009..95a8bb4 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -259,10 +260,27 @@
          *                         networks.
          */
         public Builder setNetworkSpecifier(String networkSpecifier) {
-            if (NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER.equals(networkSpecifier)) {
-                throw new IllegalArgumentException("Invalid network specifier - must not be '"
-                        + NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER + "'");
-            }
+            /*
+             * A StringNetworkSpecifier does not accept null or empty ("") strings. When network
+             * specifiers were strings a null string and an empty string were considered equivalent.
+             * Hence no meaning is attached to a null or empty ("") string.
+             */
+            return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
+                    : new StringNetworkSpecifier(networkSpecifier));
+        }
+
+        /**
+         * Sets the optional bearer specific network specifier.
+         * This has no meaning if a single transport is also not specified, so calling
+         * this without a single transport set will generate an exception, as will
+         * subsequently adding or removing transports after this is set.
+         * </p>
+         *
+         * @param networkSpecifier A concrete, parcelable framework class that extends
+         *                         NetworkSpecifier.
+         */
+        public Builder setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+            MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(networkSpecifier);
             mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
             return this;
         }
diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java
new file mode 100644
index 0000000..9ce2a5b
--- /dev/null
+++ b/core/java/android/net/NetworkSpecifier.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * Describes specific properties of a network for use in a {@link NetworkRequest}.
+ *
+ * Applications cannot instantiate this class by themselves, but can obtain instances of
+ * subclasses of this class via other APIs.
+ */
+public abstract class NetworkSpecifier {
+    /** @hide */
+    public NetworkSpecifier() {}
+
+    /**
+     * Returns true if a request with this {@link NetworkSpecifier} is satisfied by a network
+     * with the given NetworkSpecifier.
+     *
+     * @hide
+     */
+    public abstract boolean satisfiedBy(NetworkSpecifier other);
+
+    /**
+     * Optional method which can be overriden by concrete implementations of NetworkSpecifier to
+     * check a self-reported UID. A concrete implementation may contain a UID which would be self-
+     * reported by the caller (since NetworkSpecifier implementations should be non-mutable). This
+     * function is called by ConnectivityService and is passed the actual UID of the caller -
+     * allowing the verification of the self-reported UID. In cases of mismatch the implementation
+     * should throw a SecurityException.
+     *
+     * @param requestorUid The UID of the requestor as obtained from its binder.
+     *
+     * @hide
+     */
+    public void assertValidFromUid(int requestorUid) {
+        // empty
+    }
+}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index b56437e..0b1569c 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -20,6 +20,7 @@
 import android.util.Log;
 
 import com.android.internal.os.RoSystemProperties;
+import com.android.org.conscrypt.Conscrypt;
 import com.android.org.conscrypt.OpenSSLContextImpl;
 import com.android.org.conscrypt.OpenSSLSocketImpl;
 import com.android.org.conscrypt.SSLClientSessionCache;
@@ -212,7 +213,7 @@
     private SSLSocketFactory makeSocketFactory(
             KeyManager[] keyManagers, TrustManager[] trustManagers) {
         try {
-            OpenSSLContextImpl sslContext = OpenSSLContextImpl.getPreferred();
+            OpenSSLContextImpl sslContext =  (OpenSSLContextImpl) Conscrypt.newPreferredSSLContextSpi();
             sslContext.engineInit(keyManagers, trustManagers, null);
             sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache);
             return sslContext.engineGetSocketFactory();
diff --git a/core/java/android/net/StringNetworkSpecifier.java b/core/java/android/net/StringNetworkSpecifier.java
new file mode 100644
index 0000000..cb7f6bf
--- /dev/null
+++ b/core/java/android/net/StringNetworkSpecifier.java
@@ -0,0 +1,79 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/** @hide */
+public final class StringNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+    /**
+     * Arbitrary string used to pass (additional) information to the network factory.
+     */
+    public final String specifier;
+
+    public StringNetworkSpecifier(String specifier) {
+        Preconditions.checkStringNotEmpty(specifier);
+        this.specifier = specifier;
+    }
+
+    @Override
+    public boolean satisfiedBy(NetworkSpecifier other) {
+        return equals(other);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof StringNetworkSpecifier)) return false;
+        return TextUtils.equals(specifier, ((StringNetworkSpecifier) o).specifier);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(specifier);
+    }
+
+    @Override
+    public String toString() {
+        return specifier;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(specifier);
+    }
+
+    public static final Parcelable.Creator<StringNetworkSpecifier> CREATOR =
+            new Parcelable.Creator<StringNetworkSpecifier>() {
+        public StringNetworkSpecifier createFromParcel(Parcel in) {
+            return new StringNetworkSpecifier(in.readString());
+        }
+        public StringNetworkSpecifier[] newArray(int size) {
+            return new StringNetworkSpecifier[size];
+        }
+    };
+}
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 1dde3ca..27ca6e2 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -16,6 +16,10 @@
 
 package android.net.nsd;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
+
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
@@ -31,6 +35,7 @@
 
 import java.util.concurrent.CountDownLatch;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 
@@ -240,12 +245,12 @@
         return name;
     }
 
+    private static int FIRST_LISTENER_KEY = 1;
+
     private final INsdManager mService;
     private final Context mContext;
 
-    private static final int INVALID_LISTENER_KEY = 0;
-    private static final int BUSY_LISTENER_KEY = -1;
-    private int mListenerKey = 1;
+    private int mListenerKey = FIRST_LISTENER_KEY;
     private final SparseArray mListenerMap = new SparseArray();
     private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
     private final Object mMapLock = new Object();
@@ -269,6 +274,14 @@
     }
 
     /**
+     * @hide
+     */
+    @VisibleForTesting
+    public void disconnect() {
+        mAsyncChannel.disconnect();
+    }
+
+    /**
      * Failures are passed with {@link RegistrationListener#onRegistrationFailed},
      * {@link RegistrationListener#onUnregistrationFailed},
      * {@link DiscoveryListener#onStartDiscoveryFailed},
@@ -303,7 +316,6 @@
         public void onServiceFound(NsdServiceInfo serviceInfo);
 
         public void onServiceLost(NsdServiceInfo serviceInfo);
-
     }
 
     /** Interface for callback invocation for service registration */
@@ -326,15 +338,17 @@
         public void onServiceResolved(NsdServiceInfo serviceInfo);
     }
 
-    private class ServiceHandler extends Handler {
+    @VisibleForTesting
+    class ServiceHandler extends Handler {
         ServiceHandler(Looper looper) {
             super(looper);
         }
 
         @Override
         public void handleMessage(Message message) {
-            if (DBG) Log.d(TAG, "received " + nameOf(message.what));
-            switch (message.what) {
+            final int what = message.what;
+            final int key = message.arg2;
+            switch (what) {
                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                     mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
                     return;
@@ -347,19 +361,26 @@
                 default:
                     break;
             }
-            Object listener = getListener(message.arg2);
+            final Object listener;
+            final NsdServiceInfo ns;
+            synchronized (mMapLock) {
+                listener = mListenerMap.get(key);
+                ns = mServiceMap.get(key);
+            }
             if (listener == null) {
                 Log.d(TAG, "Stale key " + message.arg2);
                 return;
             }
-            NsdServiceInfo ns = getNsdService(message.arg2);
-            switch (message.what) {
+            if (DBG) {
+                Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
+            }
+            switch (what) {
                 case DISCOVER_SERVICES_STARTED:
                     String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
                     ((DiscoveryListener) listener).onDiscoveryStarted(s);
                     break;
                 case DISCOVER_SERVICES_FAILED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns),
                             message.arg1);
                     break;
@@ -370,16 +391,18 @@
                     ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj);
                     break;
                 case STOP_DISCOVERY_FAILED:
-                    removeListener(message.arg2);
+                    // TODO: failure to stop discovery should be internal and retried internally, as
+                    // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
+                    removeListener(key);
                     ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
                             message.arg1);
                     break;
                 case STOP_DISCOVERY_SUCCEEDED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns));
                     break;
                 case REGISTER_SERVICE_FAILED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1);
                     break;
                 case REGISTER_SERVICE_SUCCEEDED:
@@ -387,19 +410,21 @@
                             (NsdServiceInfo) message.obj);
                     break;
                 case UNREGISTER_SERVICE_FAILED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
                     break;
                 case UNREGISTER_SERVICE_SUCCEEDED:
+                    // TODO: do not unregister listener until service is unregistered, or provide
+                    // alternative way for unregistering ?
                     removeListener(message.arg2);
                     ((RegistrationListener) listener).onServiceUnregistered(ns);
                     break;
                 case RESOLVE_SERVICE_FAILED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((ResolveListener) listener).onResolveFailed(ns, message.arg1);
                     break;
                 case RESOLVE_SERVICE_SUCCEEDED:
-                    removeListener(message.arg2);
+                    removeListener(key);
                     ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
                     break;
                 default:
@@ -409,40 +434,27 @@
         }
     }
 
-    // if the listener is already in the map, reject it.  Otherwise, add it and
-    // return its key.
+    private int nextListenerKey() {
+        // Ensure mListenerKey >= FIRST_LISTENER_KEY;
+        mListenerKey = Math.max(FIRST_LISTENER_KEY, mListenerKey + 1);
+        return mListenerKey;
+    }
+
+    // Assert that the listener is not in the map, then add it and returns its key
     private int putListener(Object listener, NsdServiceInfo s) {
-        if (listener == null) return INVALID_LISTENER_KEY;
-        int key;
+        checkListener(listener);
+        final int key;
         synchronized (mMapLock) {
             int valueIndex = mListenerMap.indexOfValue(listener);
-            if (valueIndex != -1) {
-                return BUSY_LISTENER_KEY;
-            }
-            do {
-                key = mListenerKey++;
-            } while (key == INVALID_LISTENER_KEY);
+            checkArgument(valueIndex == -1, "listener already in use");
+            key = nextListenerKey();
             mListenerMap.put(key, listener);
             mServiceMap.put(key, s);
         }
         return key;
     }
 
-    private Object getListener(int key) {
-        if (key == INVALID_LISTENER_KEY) return null;
-        synchronized (mMapLock) {
-            return mListenerMap.get(key);
-        }
-    }
-
-    private NsdServiceInfo getNsdService(int key) {
-        synchronized (mMapLock) {
-            return mServiceMap.get(key);
-        }
-    }
-
     private void removeListener(int key) {
-        if (key == INVALID_LISTENER_KEY) return;
         synchronized (mMapLock) {
             mListenerMap.remove(key);
             mServiceMap.remove(key);
@@ -450,16 +462,15 @@
     }
 
     private int getListenerKey(Object listener) {
+        checkListener(listener);
         synchronized (mMapLock) {
             int valueIndex = mListenerMap.indexOfValue(listener);
-            if (valueIndex != -1) {
-                return mListenerMap.keyAt(valueIndex);
-            }
+            checkArgument(valueIndex != -1, "listener not registered");
+            return mListenerMap.keyAt(valueIndex);
         }
-        return INVALID_LISTENER_KEY;
     }
 
-    private String getNsdServiceInfoType(NsdServiceInfo s) {
+    private static String getNsdServiceInfoType(NsdServiceInfo s) {
         if (s == null) return "?";
         return s.getServiceType();
     }
@@ -469,7 +480,9 @@
      */
     private void init() {
         final Messenger messenger = getMessenger();
-        if (messenger == null) throw new RuntimeException("Failed to initialize");
+        if (messenger == null) {
+            fatal("Failed to obtain service Messenger");
+        }
         HandlerThread t = new HandlerThread("NsdManager");
         t.start();
         mHandler = new ServiceHandler(t.getLooper());
@@ -477,10 +490,15 @@
         try {
             mConnected.await();
         } catch (InterruptedException e) {
-            Log.e(TAG, "interrupted wait at init");
+            fatal("Interrupted wait at init");
         }
     }
 
+    private static void fatal(String msg) {
+        Log.e(TAG, msg);
+        throw new RuntimeException(msg);
+    }
+
     /**
      * Register a service to be discovered by other services.
      *
@@ -500,23 +518,10 @@
      */
     public void registerService(NsdServiceInfo serviceInfo, int protocolType,
             RegistrationListener listener) {
-        if (TextUtils.isEmpty(serviceInfo.getServiceName()) ||
-                TextUtils.isEmpty(serviceInfo.getServiceType())) {
-            throw new IllegalArgumentException("Service name or type cannot be empty");
-        }
-        if (serviceInfo.getPort() <= 0) {
-            throw new IllegalArgumentException("Invalid port number");
-        }
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
-        if (protocolType != PROTOCOL_DNS_SD) {
-            throw new IllegalArgumentException("Unsupported protocol");
-        }
+        checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
+        checkServiceInfo(serviceInfo);
+        checkProtocol(protocolType);
         int key = putListener(listener, serviceInfo);
-        if (key == BUSY_LISTENER_KEY) {
-            throw new IllegalArgumentException("listener already in use");
-        }
         mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, key, serviceInfo);
     }
 
@@ -535,12 +540,6 @@
      */
     public void unregisterService(RegistrationListener listener) {
         int id = getListenerKey(listener);
-        if (id == INVALID_LISTENER_KEY) {
-            throw new IllegalArgumentException("listener not registered");
-        }
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
         mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id);
     }
 
@@ -573,25 +572,13 @@
      * Cannot be null. Cannot be in use for an active service discovery.
      */
     public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
-        if (TextUtils.isEmpty(serviceType)) {
-            throw new IllegalArgumentException("Service type cannot be empty");
-        }
-
-        if (protocolType != PROTOCOL_DNS_SD) {
-            throw new IllegalArgumentException("Unsupported protocol");
-        }
+        checkStringNotEmpty(serviceType, "Service type cannot be empty");
+        checkProtocol(protocolType);
 
         NsdServiceInfo s = new NsdServiceInfo();
         s.setServiceType(serviceType);
 
         int key = putListener(listener, s);
-        if (key == BUSY_LISTENER_KEY) {
-            throw new IllegalArgumentException("listener already in use");
-        }
-
         mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, key, s);
     }
 
@@ -613,12 +600,6 @@
      */
     public void stopServiceDiscovery(DiscoveryListener listener) {
         int id = getListenerKey(listener);
-        if (id == INVALID_LISTENER_KEY) {
-            throw new IllegalArgumentException("service discovery not active on listener");
-        }
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
         mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id);
     }
 
@@ -632,19 +613,8 @@
      * Cannot be in use for an active service resolution.
      */
     public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
-        if (TextUtils.isEmpty(serviceInfo.getServiceName()) ||
-                TextUtils.isEmpty(serviceInfo.getServiceType())) {
-            throw new IllegalArgumentException("Service name or type cannot be empty");
-        }
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
-
+        checkServiceInfo(serviceInfo);
         int key = putListener(listener, serviceInfo);
-
-        if (key == BUSY_LISTENER_KEY) {
-            throw new IllegalArgumentException("listener already in use");
-        }
         mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, key, serviceInfo);
     }
 
@@ -658,10 +628,10 @@
     }
 
     /**
-     * Get a reference to NetworkService handler. This is used to establish
+     * Get a reference to NsdService handler. This is used to establish
      * an AsyncChannel communication with the service
      *
-     * @return Messenger pointing to the NetworkService handler
+     * @return Messenger pointing to the NsdService handler
      */
     private Messenger getMessenger() {
         try {
@@ -670,4 +640,18 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    private static void checkListener(Object listener) {
+        checkNotNull(listener, "listener cannot be null");
+    }
+
+    private static void checkProtocol(int protocolType) {
+        checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol");
+    }
+
+    private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
+        checkNotNull(serviceInfo, "NsdServiceInfo cannot be null");
+        checkStringNotEmpty(serviceInfo.getServiceName(),"Service name cannot be empty");
+        checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty");
+    }
 }
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index 94fd5b0..4ba1144 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.java
@@ -209,10 +209,11 @@
     public native final IHwBinder readStrongBinder();
 
     // Handle is stored as part of the blob.
-    public native final HwBlob readBuffer();
+    public native final HwBlob readBuffer(long expectedSize);
 
     public native final HwBlob readEmbeddedBuffer(
-            long parentHandle, long offset, boolean nullable);
+            long expectedSize, long parentHandle, long offset,
+            boolean nullable);
 
     public native final void writeBuffer(HwBlob blob);
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index dbe2f6d..c34de15 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -315,8 +315,6 @@
     void setFirewallEnabled(boolean enabled);
     boolean isFirewallEnabled();
     void setFirewallInterfaceRule(String iface, boolean allow);
-    void setFirewallEgressSourceRule(String addr, boolean allow);
-    void setFirewallEgressDestRule(String addr, int port, boolean allow);
     void setFirewallUidRule(int chain, int uid, int rule);
     void setFirewallUidRules(int chain, in int[] uids, in int[] rules);
     void setFirewallChainEnabled(int chain, boolean enable);
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 4f4152c..2a8ae93 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -728,9 +728,11 @@
             int timeTotal = -1;
             int uncryptTime = -1;
             int sourceVersion = -1;
-            int temperature_start = -1;
-            int temperature_end = -1;
-            int temperature_max = -1;
+            int temperatureStart = -1;
+            int temperatureEnd = -1;
+            int temperatureMax = -1;
+            int errorCode = -1;
+            int causeCode = -1;
 
             while ((line = in.readLine()) != null) {
                 // Here is an example of lines in last_install:
@@ -777,11 +779,15 @@
                     bytesStashedInMiB = (bytesStashedInMiB == -1) ? scaled :
                             bytesStashedInMiB + scaled;
                 } else if (line.startsWith("temperature_start")) {
-                    temperature_start = scaled;
+                    temperatureStart = scaled;
                 } else if (line.startsWith("temperature_end")) {
-                    temperature_end = scaled;
+                    temperatureEnd = scaled;
                 } else if (line.startsWith("temperature_max")) {
-                    temperature_max = scaled;
+                    temperatureMax = scaled;
+                } else if (line.startsWith("error")) {
+                    errorCode = scaled;
+                } else if (line.startsWith("cause")) {
+                    causeCode = scaled;
                 }
             }
 
@@ -801,14 +807,20 @@
             if (bytesStashedInMiB != -1) {
                 MetricsLogger.histogram(context, "ota_stashed_in_MiBs", bytesStashedInMiB);
             }
-            if (temperature_start != -1) {
-                MetricsLogger.histogram(context, "ota_temperature_start", temperature_start);
+            if (temperatureStart != -1) {
+                MetricsLogger.histogram(context, "ota_temperature_start", temperatureStart);
             }
-            if (temperature_end != -1) {
-                MetricsLogger.histogram(context, "ota_temperature_end", temperature_end);
+            if (temperatureEnd != -1) {
+                MetricsLogger.histogram(context, "ota_temperature_end", temperatureEnd);
             }
-            if (temperature_max != -1) {
-                MetricsLogger.histogram(context, "ota_temperature_max", temperature_max);
+            if (temperatureMax != -1) {
+                MetricsLogger.histogram(context, "ota_temperature_max", temperatureMax);
+            }
+            if (errorCode != -1) {
+                MetricsLogger.histogram(context, "ota_non_ab_error_code", errorCode);
+            }
+            if (causeCode != -1) {
+                MetricsLogger.histogram(context, "ota_non_ab_cause_code", causeCode);
             }
 
         } catch (IOException e) {
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
new file mode 100644
index 0000000..65b33e5
--- /dev/null
+++ b/core/java/android/os/VintfObject.java
@@ -0,0 +1,73 @@
+/*
+ * 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.os;
+
+import java.util.Map;
+
+import android.util.Log;
+
+/**
+ * Java API for libvintf.
+ * @hide
+ */
+public class VintfObject {
+
+    /// ---------- OTA
+
+    /**
+     * Slurps all device information (both manifests and both matrices)
+     * and report them.
+     * If any error in getting one of the manifests, it is not included in
+     * the list.
+     */
+    public static native String[] report();
+
+    /**
+     * Verify that the given metadata for an OTA package is compatible with
+     * this device.
+     *
+     * @param packageInfo a list of serialized form of HalMaanifest's /
+     * CompatibilityMatri'ces (XML).
+     * @return = 0 if success (compatible)
+     *         > 0 if incompatible
+     *         < 0 if any error (mount partition fails, illformed XML, etc.)
+     */
+    public static native int verify(String[] packageInfo);
+
+    /// ---------- CTS Device Info
+
+    /**
+     * @return a list of HAL names and versions that is supported by this
+     * device as stated in device and framework manifests. For example,
+     * ["android.hidl.manager@1.0", "android.hardware.camera.device@1.0",
+     *  "android.hardware.camera.device@3.2"]. There are no duplicates.
+     */
+    public static native String[] getHalNamesAndVersions();
+
+    /**
+     * @return the BOARD_SEPOLICY_VERS build flag available in device manifest.
+     */
+    public static native String getSepolicyVersion();
+
+    /**
+     * @return a list of VNDK snapshots supported by the framework, as
+     * specified in framework manifest. For example,
+     * [("25.0.5", ["libjpeg.so", "libbase.so"]),
+     *  ("25.1.3", ["libjpeg.so", "libbase.so"])]
+     */
+    public static native Map<String, String[]> getVndkSnapshots();
+}
diff --git a/core/java/android/os/VintfRuntimeInfo.java b/core/java/android/os/VintfRuntimeInfo.java
new file mode 100644
index 0000000..29698b9
--- /dev/null
+++ b/core/java/android/os/VintfRuntimeInfo.java
@@ -0,0 +1,70 @@
+/*
+ * 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.os;
+
+/**
+ * Java API for ::android::vintf::RuntimeInfo. Methods return null / 0 on any error.
+ *
+ * @hide
+ */
+public class VintfRuntimeInfo {
+
+    private VintfRuntimeInfo() {}
+
+    /**
+     * @return /sys/fs/selinux/policyvers, via security_policyvers() native call
+     */
+    public static native long getKernelSepolicyVersion();
+    /**
+     * @return content of /proc/cpuinfo
+     */
+    public static native String getCpuInfo();
+    /**
+     * @return os name extracted from uname() native call
+     */
+    public static native String getOsName();
+    /**
+     * @return node name extracted from uname() native call
+     */
+    public static native String getNodeName();
+    /**
+     * @return os release extracted from uname() native call
+     */
+    public static native String getOsRelease();
+    /**
+     * @return os version extracted from uname() native call
+     */
+    public static native String getOsVersion();
+    /**
+     * @return hardware id extracted from uname() native call
+     */
+    public static native String getHardwareId();
+    /**
+     * @return kernel version extracted from uname() native call. Format is
+     * {@code x.y.z}.
+     */
+    public static native String getKernelVersion();
+    /**
+     * @return libavb version in OS. Format is {@code x.y}.
+     */
+    public static native String getBootAvbVersion();
+    /**
+     * @return libavb version in bootloader. Format is {@code x.y}.
+     */
+    public static native String getBootVbmetaAvbVersion();
+
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8e55f4b..2c1796370 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4720,13 +4720,6 @@
         public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
 
         /**
-         * bluetooth HCI snoop log configuration
-         * @hide
-         */
-        public static final String BLUETOOTH_HCI_LOG =
-                "bluetooth_hci_log";
-
-        /**
          * @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
          */
         @Deprecated
@@ -8183,6 +8176,15 @@
         public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
 
         /**
+         * A comma separated list of URLs used for captive portal detection in addition to the
+         * fallback HTTP url associated with the CAPTIVE_PORTAL_FALLBACK_URL settings.
+         *
+         * @hide
+         */
+        public static final String CAPTIVE_PORTAL_OTHER_FALLBACK_URLS =
+                "captive_portal_other_fallback_urls";
+
+        /**
          * Whether to use HTTPS for network validation. This is enabled by default and the setting
          * needs to be set to 0 to disable it. This setting is a misnomer because captive portals
          * don't actually use HTTPS, but it's consistent with the other settings.
@@ -9112,7 +9114,8 @@
             CALL_AUTO_RETRY,
             DOCK_AUDIO_MEDIA_ENABLED,
             ENCODED_SURROUND_OUTPUT,
-            LOW_POWER_MODE_TRIGGER_LEVEL
+            LOW_POWER_MODE_TRIGGER_LEVEL,
+            BLUETOOTH_ON
         };
 
         // Populated lazily, guarded by class object:
diff --git a/core/java/android/provider/TimeZoneRulesDataContract.java b/core/java/android/provider/TimeZoneRulesDataContract.java
new file mode 100644
index 0000000..19e914b
--- /dev/null
+++ b/core/java/android/provider/TimeZoneRulesDataContract.java
@@ -0,0 +1,114 @@
+/*
+ * 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.provider;
+
+import android.net.Uri;
+
+/**
+ * A set of constants for implementing a time zone data content provider, which is used by the time
+ * zone updater application.
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class TimeZoneRulesDataContract {
+
+    private TimeZoneRulesDataContract() {}
+
+    /**
+     * The authority that <em>must</em> be used for the time zone data content provider.
+     * To be accepted by the time zone updater application it <em>must</em> be exposed by the
+     * package specified in the config_timeZoneRulesDataPackage config value.
+     */
+    public static final String AUTHORITY = "com.android.timezone";
+
+    /** A content:// style uri to the authority for the time zone data content provider */
+    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.
+     */
+    public static final Uri OPERATION_URI = Uri.withAppendedPath(AUTHORITY_URI, "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";
+
+    /**
+     * 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";
+
+    /**
+     * 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";
+
+    /**
+     * 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";
+
+    /**
+     * 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";
+
+    /**
+     * 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";
+
+    /**
+     * 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 #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 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");
+}
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 1e54163..a8b094e 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -107,15 +107,7 @@
 
     /**
      * Broadcast intent to inform a new visual voicemail SMS has been received. This intent will
-     * only be delivered to the voicemail client. The intent will have the following extra values:
-     *
-     * <ul>
-     *   <li><em>{@link #EXTRA_VOICEMAIL_SMS_TYPE}</em> - (String) The event type of the SMS. Common
-     *   values are "SYNC" or "STATUS"</li>
-     *   <li><em>{@link #EXTRA_VOICEMAIL_SMS_DATA}</em> - (Bundle) The fields sent by the SMS</li>
-     *   <li><em>{@link #EXTRA_VOICEMAIL_SMS_SUBID}</em> - (Integer) The subscription ID of the
-     *   phone account that received the SMS</li>
-     * </ul>
+     * only be delivered to the telephony service. {@link #EXTRA_VOICEMAIL_SMS} will be included.
      */
     /** @hide */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -123,42 +115,11 @@
             "android.intent.action.VOICEMAIL_SMS_RECEIVED";
 
     /**
-     * Optional extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to
-     * indicate the event type of the SMS. Common values are "SYNC" or "STATUS". The extra will not
-     * exist if the framework cannot parse the SMS as voicemail but the carrier pattern indicates
-     * it is.
-     */
-    /** @hide */
-    public static final String EXTRA_VOICEMAIL_SMS_PREFIX =
-            "com.android.voicemail.extra.VOICEMAIL_SMS_PREFIX";
-
-    /**
-     * Optional extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to
-     * indicate the fields sent by the SMS. The extra will not exist if the framework cannot
-     * parse the SMS as voicemail but the carrier pattern indicates it is.
-     */
-    /** @hide */
-    public static final String EXTRA_VOICEMAIL_SMS_FIELDS =
-            "com.android.voicemail.extra.VOICEMAIL_SMS_FIELDS";
-
-    /**
-     * Extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to indicate the
-     * message body of the SMS. This extra is included if the framework cannot
-     * parse the SMS as voicemail but the carrier pattern indicates it is.
-     */
-    /**
+     * Extra in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} indicating the content of the SMS.
+     *
      * @hide
      */
-    public static final String EXTRA_VOICEMAIL_SMS_MESSAGE_BODY =
-        "com.android.voicemail.extra.VOICEMAIL_SMS_MESSAGE_BODY";
-
-    /**
-     * Extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to indicate he
-     * subscription ID of the phone account that received the SMS.
-     */
-    /** @hide */
-    public static final String EXTRA_VOICEMAIL_SMS_SUBID =
-            "com.android.voicemail.extra.VOICEMAIL_SMS_SUBID";
+    public static final String EXTRA_VOICEMAIL_SMS = "android.provider.extra.VOICEMAIL_SMS";
 
     /**
      * Extra included in {@link Intent#ACTION_PROVIDER_CHANGED} broadcast intents to indicate if the
@@ -323,8 +284,6 @@
          * not.
          *
          * <P>Type: INTEGER (boolean)</P>
-         *
-         * @hide
          */
         public static final String BACKED_UP = "backed_up";
 
@@ -333,8 +292,6 @@
          * restored, 0 if not.
          *
          * <P>Type: INTEGER (boolean)</P>
-         *
-         * @hide
          */
         public static final String RESTORED = "restored";
 
@@ -344,19 +301,19 @@
          * if not.
          *
          * <P>Type: INTEGER (boolean)</P>
-         *
-         * @hide
          */
         public static final String ARCHIVED = "archived";
 
         /**
          * Flag to indicate the voicemail is a OMTP voicemail handled by the {@link
          * android.telephony.VisualVoicemailService}. The UI should only show OMTP voicemails from
-         * the current visual voicemail package.
+         * the current visual voicemail package. For example, the selection could be
+         * {@code WHERE (IS_OMTP_VOICEMAIL == 0) OR ( IS_OMTP_VOICEMAIL == 1 AND SOURCE_PACKAGE ==
+         * "current.vvm.package")}
          *
          * <P>Type: INTEGER (boolean)</P>
          *
-         * @hide
+         * @see android.telephony.TelephonyManager#getVisualVoicemailPackageName
          */
         public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
 
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 5bc6c94..951aa8d 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -89,8 +89,9 @@
 
     /**
      * Exception class used to capture a stack trace in {@link #wtf}.
+     * @hide
      */
-    private static class TerribleFailure extends Exception {
+    public static class TerribleFailure extends Exception {
         TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
     }
 
@@ -212,7 +213,9 @@
      * @param tag The tag to check.
      * @param level The level to check.
      * @return Whether or not that this is allowed to be logged.
-     * @throws IllegalArgumentException is thrown if the tag.length() > 23.
+     * @throws IllegalArgumentException is thrown if the tag.length() > 23
+     *         for Nougat (7.0) releases (API <= 23) and prior, there is no
+     *         tag limit of concern after this API level.
      */
     public static native boolean isLoggable(String tag, int level);
 
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index 0d62054..b72e783 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -53,7 +53,7 @@
 
     private final boolean mIsOwner;
     private final long mMemoryAddr;
-    private int mFd;
+    private int mFd = -1;
 
     /**
      * Creates a new instance.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2e9cbf2..eef1cf5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1920,7 +1920,7 @@
                 Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER,
                         mPendingEvents.size());
 
-                Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, p);
+                Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, seq, 0, p);
                 msg.setAsynchronous(true);
                 mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);
                 return DISPATCH_IN_PROGRESS;
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 506114b..3d3e148 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -50,6 +50,11 @@
     private static final boolean USE_NATIVE_PARSING = true;
     private static final boolean SANITY_CHECK_NATIVE = false;
 
+    private static final String CLATD_INTERFACE_PREFIX = "v4-";
+    // Delta between IPv4 header (20b) and IPv6 header (40b).
+    // Used for correct stats accounting on clatd interfaces.
+    private static final int IPV4V6_HEADER_DELTA = 20;
+
     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
     private final File mStatsXtIfaceAll;
     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */
@@ -57,6 +62,7 @@
     /** Path to {@code /proc/net/xt_qtaguid/stats}. */
     private final File mStatsXtUid;
 
+    // TODO: to improve testability and avoid global state, do not use a static variable.
     @GuardedBy("sStackedIfaces")
     private static final ArrayMap<String, String> sStackedIfaces = new ArrayMap<>();
 
@@ -124,9 +130,7 @@
                 stats.addValues(entry);
                 reader.finishLine();
             }
-        } catch (NullPointerException e) {
-            throw new ProtocolException("problem parsing stats", e);
-        } catch (NumberFormatException e) {
+        } catch (NullPointerException|NumberFormatException e) {
             throw new ProtocolException("problem parsing stats", e);
         } finally {
             IoUtils.closeQuietly(reader);
@@ -171,9 +175,7 @@
                 stats.addValues(entry);
                 reader.finishLine();
             }
-        } catch (NullPointerException e) {
-            throw new ProtocolException("problem parsing stats", e);
-        } catch (NumberFormatException e) {
+        } catch (NullPointerException|NumberFormatException e) {
             throw new ProtocolException("problem parsing stats", e);
         } finally {
             IoUtils.closeQuietly(reader);
@@ -188,48 +190,54 @@
 
     public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag,
             NetworkStats lastStats) throws IOException {
-        final NetworkStats stats = readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag,
-                lastStats);
-
+        final NetworkStats stats =
+              readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag, lastStats);
+        final ArrayMap<String, String> stackedIfaces;
         synchronized (sStackedIfaces) {
-            // Sigh, xt_qtaguid ends up double-counting tx traffic going through
-            // clatd interfaces, so we need to subtract it here.
-            final int size = sStackedIfaces.size();
-            for (int i = 0; i < size; i++) {
-                final String stackedIface = sStackedIfaces.keyAt(i);
-                final String baseIface = sStackedIfaces.valueAt(i);
-
-                // Count up the tx traffic and subtract from root UID on the
-                // base interface.
-                NetworkStats.Entry adjust = new NetworkStats.Entry(baseIface, 0, 0, 0, 0L, 0L, 0L,
-                        0L, 0L);
-                NetworkStats.Entry entry = null;
-                for (int j = 0; j < stats.size(); j++) {
-                    entry = stats.getValues(j, entry);
-                    if (Objects.equals(entry.iface, stackedIface)) {
-                        adjust.txBytes -= entry.txBytes;
-                        adjust.txPackets -= entry.txPackets;
-                    }
-                }
-                stats.combineValues(adjust);
-            }
+            stackedIfaces = new ArrayMap<>(sStackedIfaces);
         }
+        // Total 464xlat traffic to subtract from uid 0 on all base interfaces.
+        final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size());
 
-        // Double sigh, all rx traffic on clat needs to be tweaked to
-        // account for the dropped IPv6 header size post-unwrap.
-        NetworkStats.Entry entry = null;
+        NetworkStats.Entry entry = null; // For recycling
+
+        // For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4
+        // packet on the stacked interface, and once as translated to an IPv6 packet on the
+        // base interface. For correct stats accounting on the base interface, every 464xlat
+        // packet needs to be subtracted from the root UID on the base interface both for tx
+        // and rx traffic (http://b/12249687, http:/b/33681750).
         for (int i = 0; i < stats.size(); i++) {
             entry = stats.getValues(i, entry);
-            if (entry.iface != null && entry.iface.startsWith("clat")) {
-                // Delta between IPv4 header (20b) and IPv6 header (40b)
-                entry.rxBytes = entry.rxPackets * 20;
-                entry.rxPackets = 0;
-                entry.txBytes = 0;
-                entry.txPackets = 0;
-                stats.combineValues(entry);
+            if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) {
+                continue;
             }
+            final String baseIface = stackedIfaces.get(entry.iface);
+            if (baseIface == null) {
+                continue;
+            }
+
+            NetworkStats.Entry adjust =
+                    new NetworkStats.Entry(baseIface, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);
+            // Subtract any 464lat traffic seen for the root UID on the current base interface.
+            adjust.rxBytes -= (entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
+            adjust.txBytes -= (entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
+            adjust.rxPackets -= entry.rxPackets;
+            adjust.txPackets -= entry.txPackets;
+            adjustments.combineValues(adjust);
+
+            // For 464xlat traffic, xt_qtaguid only counts the bytes of the native IPv4 packet sent
+            // on the stacked interface with prefix "v4-" and drops the IPv6 header size after
+            // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes
+            // difference for all packets (http://b/12249687, http:/b/33681750).
+            entry.rxBytes = entry.rxPackets * IPV4V6_HEADER_DELTA;
+            entry.txBytes = entry.txPackets * IPV4V6_HEADER_DELTA;
+            entry.rxPackets = 0;
+            entry.txPackets = 0;
+            stats.combineValues(entry);
         }
 
+        stats.combineAllValues(adjustments);
+
         return stats;
     }
 
@@ -305,9 +313,7 @@
 
                 reader.finishLine();
             }
-        } catch (NullPointerException e) {
-            throw new ProtocolException("problem parsing idx " + idx, e);
-        } catch (NumberFormatException e) {
+        } catch (NullPointerException|NumberFormatException e) {
             throw new ProtocolException("problem parsing idx " + idx, e);
         } finally {
             IoUtils.closeQuietly(reader);
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 527582b..91429a6 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -717,14 +717,6 @@
     public static void applyInvokeWithSystemProperty(Arguments args) {
         if (args.invokeWith == null && args.niceName != null) {
             String property = "wrap." + args.niceName;
-            if (property.length() > 31) {
-                // Properties with a trailing "." are illegal.
-                if (property.charAt(30) != '.') {
-                    property = property.substring(0, 31);
-                } else {
-                    property = property.substring(0, 30);
-                }
-            }
             args.invokeWith = SystemProperties.get(property);
             if (args.invokeWith != null && args.invokeWith.length() == 0) {
                 args.invokeWith = null;
diff --git a/core/java/com/android/internal/util/BitUtils.java b/core/java/com/android/internal/util/BitUtils.java
new file mode 100644
index 0000000..1b354d0
--- /dev/null
+++ b/core/java/com/android/internal/util/BitUtils.java
@@ -0,0 +1,127 @@
+/*
+ * 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 android.annotation.Nullable;
+
+import libcore.util.Objects;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * A utility class for handling unsigned integers and unsigned arithmetics, as well as syntactic
+ * sugar methods for ByteBuffer. Useful for networking and packet manipulations.
+ * {@hide}
+ */
+public final class BitUtils {
+    private BitUtils() {}
+
+    public static boolean maskedEquals(long a, long b, long mask) {
+        return (a & mask) == (b & mask);
+    }
+
+    public static boolean maskedEquals(byte a, byte b, byte mask) {
+        return (a & mask) == (b & mask);
+    }
+
+    public static boolean maskedEquals(byte[] a, byte[] b, @Nullable byte[] mask) {
+        if (a == null || b == null) return a == b;
+        Preconditions.checkArgument(a.length == b.length, "Inputs must be of same size");
+        if (mask == null) return Arrays.equals(a, b);
+        Preconditions.checkArgument(a.length == mask.length, "Mask must be of same size as inputs");
+        for (int i = 0; i < mask.length; i++) {
+            if (!maskedEquals(a[i], b[i], mask[i])) return false;
+        }
+        return true;
+    }
+
+    public static boolean maskedEquals(UUID a, UUID b, @Nullable UUID mask) {
+        if (mask == null) {
+            return Objects.equal(a, b);
+        }
+        return maskedEquals(a.getLeastSignificantBits(), b.getLeastSignificantBits(),
+                    mask.getLeastSignificantBits())
+                && maskedEquals(a.getMostSignificantBits(), b.getMostSignificantBits(),
+                    mask.getMostSignificantBits());
+    }
+
+    public static int[] unpackBits(long val) {
+        int size = Long.bitCount(val);
+        int[] result = new int[size];
+        int index = 0;
+        int bitPos = 0;
+        while (val > 0) {
+            if ((val & 1) == 1) result[index++] = bitPos;
+            val = val >> 1;
+            bitPos++;
+        }
+        return result;
+    }
+
+    public static long packBits(int[] bits) {
+        long packed = 0;
+        for (int b : bits) {
+            packed |= (1 << b);
+        }
+        return packed;
+    }
+
+    public static int uint8(byte b) {
+        return b & 0xff;
+    }
+
+    public static int uint16(short s) {
+        return s & 0xffff;
+    }
+
+    public static long uint32(int i) {
+        return i & 0xffffffffL;
+    }
+
+    public static int bytesToBEInt(byte[] bytes) {
+        return (uint8(bytes[0]) << 24)
+                + (uint8(bytes[1]) << 16)
+                + (uint8(bytes[2]) << 8)
+                + (uint8(bytes[3]));
+    }
+
+    public static int bytesToLEInt(byte[] bytes) {
+        return Integer.reverseBytes(bytesToBEInt(bytes));
+    }
+
+    public static int getUint8(ByteBuffer buffer, int position) {
+        return uint8(buffer.get(position));
+    }
+
+    public static int getUint16(ByteBuffer buffer, int position) {
+        return uint16(buffer.getShort(position));
+    }
+
+    public static long getUint32(ByteBuffer buffer, int position) {
+        return uint32(buffer.getInt(position));
+    }
+
+    public static void put(ByteBuffer buffer, int position, byte[] bytes) {
+        final int original = buffer.position();
+        buffer.position(position);
+        buffer.put(bytes);
+        buffer.position(original);
+    }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
new file mode 100644
index 0000000..c8784f8
--- /dev/null
+++ b/core/jni/Android.bp
@@ -0,0 +1,285 @@
+cc_library_shared {
+    name: "libandroid_runtime",
+
+    cflags: [
+        "-Wno-unused-parameter",
+        "-Wno-non-virtual-dtor",
+        "-Wno-maybe-uninitialized",
+        "-Wno-parentheses",
+
+        "-DHWUI_NEW_OPS",
+
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DEGL_EGLEXT_PROTOTYPES",
+
+        "-DU_USING_ICU_NAMESPACE=0",
+
+        "-Wall",
+        "-Werror",
+        "-Wno-error=deprecated-declarations",
+        "-Wunused",
+        "-Wunreachable-code",
+
+        // necessary for Clang as the GL bindings need to turn
+        // off a GCC warning that Clang doesn't know.
+        "-Wno-unknown-pragmas",
+    ],
+
+    cppflags: ["-Wno-conversion-null"],
+
+    srcs: [
+        "AndroidRuntime.cpp",
+        "com_android_internal_content_NativeLibraryHelper.cpp",
+        "com_google_android_gles_jni_EGLImpl.cpp",
+        "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm
+        "android_app_Activity.cpp",
+        "android_app_ApplicationLoaders.cpp",
+        "android_app_NativeActivity.cpp",
+        "android_app_admin_SecurityLog.cpp",
+        "android_opengl_EGL14.cpp",
+        "android_opengl_EGLExt.cpp",
+        "android_opengl_GLES10.cpp",
+        "android_opengl_GLES10Ext.cpp",
+        "android_opengl_GLES11.cpp",
+        "android_opengl_GLES11Ext.cpp",
+        "android_opengl_GLES20.cpp",
+        "android_opengl_GLES30.cpp",
+        "android_opengl_GLES31.cpp",
+        "android_opengl_GLES31Ext.cpp",
+        "android_opengl_GLES32.cpp",
+        "android_database_CursorWindow.cpp",
+        "android_database_SQLiteCommon.cpp",
+        "android_database_SQLiteConnection.cpp",
+        "android_database_SQLiteGlobal.cpp",
+        "android_database_SQLiteDebug.cpp",
+        "android_graphics_drawable_AnimatedVectorDrawable.cpp",
+        "android_graphics_drawable_VectorDrawable.cpp",
+        "android_view_DisplayEventReceiver.cpp",
+        "android_view_DisplayListCanvas.cpp",
+        "android_view_GraphicBuffer.cpp",
+        "android_view_HardwareLayer.cpp",
+        "android_view_InputChannel.cpp",
+        "android_view_InputDevice.cpp",
+        "android_view_InputEventReceiver.cpp",
+        "android_view_InputEventSender.cpp",
+        "android_view_InputQueue.cpp",
+        "android_view_KeyCharacterMap.cpp",
+        "android_view_KeyEvent.cpp",
+        "android_view_MotionEvent.cpp",
+        "android_view_PointerIcon.cpp",
+        "android_view_RenderNode.cpp",
+        "android_view_RenderNodeAnimator.cpp",
+        "android_view_Surface.cpp",
+        "android_view_SurfaceControl.cpp",
+        "android_view_SurfaceSession.cpp",
+        "android_view_TextureView.cpp",
+        "android_view_ThreadedRenderer.cpp",
+        "android_view_VelocityTracker.cpp",
+        "android_text_AndroidCharacter.cpp",
+        "android_text_AndroidBidi.cpp",
+        "android_text_StaticLayout.cpp",
+        "android_os_Debug.cpp",
+        "android_os_GraphicsEnvironment.cpp",
+        "android_os_HwBinder.cpp",
+        "android_os_HwBlob.cpp",
+        "android_os_HwParcel.cpp",
+        "android_os_HwRemoteBinder.cpp",
+        "android_os_MemoryFile.cpp",
+        "android_os_MessageQueue.cpp",
+        "android_os_Parcel.cpp",
+        "android_os_SELinux.cpp",
+        "android_os_seccomp.cpp",
+        "android_os_SystemClock.cpp",
+        "android_os_SystemProperties.cpp",
+        "android_os_Trace.cpp",
+        "android_os_UEventObserver.cpp",
+        "android_os_VintfObject.cpp",
+        "android_os_VintfRuntimeInfo.cpp",
+        "android_net_LocalSocketImpl.cpp",
+        "android_net_NetUtils.cpp",
+        "android_net_TrafficStats.cpp",
+        "android_nio_utils.cpp",
+        "android_util_AssetManager.cpp",
+        "android_util_Binder.cpp",
+        "android_util_EventLog.cpp",
+        "android_util_MemoryIntArray.cpp",
+        "android_util_Log.cpp",
+        "android_util_PathParser.cpp",
+        "android_util_Process.cpp",
+        "android_util_StringBlock.cpp",
+        "android_util_XmlBlock.cpp",
+        "android_util_jar_StrictJarFile.cpp",
+        "android_graphics_Canvas.cpp",
+        "android_graphics_Picture.cpp",
+        "android/graphics/Bitmap.cpp",
+        "android/graphics/BitmapFactory.cpp",
+        "android/graphics/Camera.cpp",
+        "android/graphics/CanvasProperty.cpp",
+        "android/graphics/ColorFilter.cpp",
+        "android/graphics/DrawFilter.cpp",
+        "android/graphics/FontFamily.cpp",
+        "android/graphics/CreateJavaOutputStreamAdaptor.cpp",
+        "android/graphics/Graphics.cpp",
+        "android/graphics/HarfBuzzNGFaceSkia.cpp",
+        "android/graphics/Interpolator.cpp",
+        "android/graphics/MaskFilter.cpp",
+        "android/graphics/Matrix.cpp",
+        "android/graphics/Movie.cpp",
+        "android/graphics/NinePatch.cpp",
+        "android/graphics/NinePatchPeeker.cpp",
+        "android/graphics/Paint.cpp",
+        "android/graphics/Path.cpp",
+        "android/graphics/PathMeasure.cpp",
+        "android/graphics/PathEffect.cpp",
+        "android/graphics/Picture.cpp",
+        "android/graphics/PorterDuff.cpp",
+        "android/graphics/BitmapRegionDecoder.cpp",
+        "android/graphics/Rasterizer.cpp",
+        "android/graphics/Region.cpp",
+        "android/graphics/Shader.cpp",
+        "android/graphics/SurfaceTexture.cpp",
+        "android/graphics/Typeface.cpp",
+        "android/graphics/Utils.cpp",
+        "android/graphics/Xfermode.cpp",
+        "android/graphics/YuvToJpegEncoder.cpp",
+        "android/graphics/pdf/PdfDocument.cpp",
+        "android/graphics/pdf/PdfEditor.cpp",
+        "android/graphics/pdf/PdfRenderer.cpp",
+        "android_media_AudioRecord.cpp",
+        "android_media_AudioSystem.cpp",
+        "android_media_AudioTrack.cpp",
+        "android_media_DeviceCallback.cpp",
+        "android_media_JetPlayer.cpp",
+        "android_media_RemoteDisplay.cpp",
+        "android_media_ToneGenerator.cpp",
+        "android_hardware_Camera.cpp",
+        "android_hardware_camera2_CameraMetadata.cpp",
+        "android_hardware_camera2_legacy_LegacyCameraDevice.cpp",
+        "android_hardware_camera2_legacy_PerfMeasurement.cpp",
+        "android_hardware_camera2_DngCreator.cpp",
+        "android_hardware_Radio.cpp",
+        "android_hardware_SensorManager.cpp",
+        "android_hardware_SerialPort.cpp",
+        "android_hardware_SoundTrigger.cpp",
+        "android_hardware_UsbDevice.cpp",
+        "android_hardware_UsbDeviceConnection.cpp",
+        "android_hardware_UsbRequest.cpp",
+        "android_hardware_location_ContextHubService.cpp",
+        "android_hardware_location_ActivityRecognitionHardware.cpp",
+        "android_util_FileObserver.cpp",
+        "android/opengl/poly_clip.cpp", // TODO: .arm
+        "android/opengl/util.cpp",
+        "android_server_NetworkManagementSocketTagger.cpp",
+        "android_server_Watchdog.cpp",
+        "android_ddm_DdmHandleNativeHeap.cpp",
+        "android_backup_BackupDataInput.cpp",
+        "android_backup_BackupDataOutput.cpp",
+        "android_backup_FileBackupHelperBase.cpp",
+        "android_backup_BackupHelperDispatcher.cpp",
+        "android_app_backup_FullBackup.cpp",
+        "android_content_res_ObbScanner.cpp",
+        "android_content_res_Configuration.cpp",
+        "android_animation_PropertyValuesHolder.cpp",
+        "com_android_internal_net_NetworkStatsFactory.cpp",
+        "com_android_internal_os_PathClassLoaderFactory.cpp",
+        "com_android_internal_os_Zygote.cpp",
+        "com_android_internal_util_VirtualRefBasePtr.cpp",
+        "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp",
+        "hwbinder/EphemeralStorage.cpp",
+        "fd_utils.cpp",
+    ],
+
+    include_dirs: [
+        // we need to access the private Bionic header
+        // <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
+        "bionic/libc/private",
+
+        "frameworks/native/opengl/libs",
+
+        "external/skia/include/private",
+        "external/skia/src/core",
+        "external/skia/src/effects",
+        "external/skia/src/images",
+        "frameworks/base/media/jni",
+        "libcore/include",
+   ],
+
+    static_libs: [
+        "libseccomp_policy",
+        "libselinux",
+        "libcrypto",
+    ],
+
+    shared_libs: [
+        "libmemtrack",
+        "libandroidfw",
+        "libbase",
+        "libexpat",
+        "libnativehelper",
+        "liblog",
+        "libcutils",
+        "libdebuggerd_client",
+        "libutils",
+        "libbinder",
+        "libnetutils",
+        "libui",
+        "libgui",
+        "libinput",
+        "libinputflinger",
+        "libcamera_client",
+        "libcamera_metadata",
+        "libskia",
+        "libsqlite",
+        "libEGL",
+        "libGLESv1_CM",
+        "libGLESv2",
+        "libvulkan",
+        "libETC1",
+        "libhardware",
+        "libhardware_legacy",
+        "libselinux",
+        "libsonivox",
+        "libcrypto",
+        "libssl",
+        "libicuuc",
+        "libicui18n",
+        "libmedia",
+        "libaudioclient",
+        "libjpeg",
+        "libusbhost",
+        "libharfbuzz_ng",
+        "libz",
+        "libaudioutils",
+        "libpdfium",
+        "libimg_utils",
+        "libnetd_client",
+        "libradio",
+        "libsoundtrigger",
+        "libminikin",
+        "libprocessgroup",
+        "libnativebridge",
+        "libradio_metadata",
+        "libnativeloader",
+        "libmemunreachable",
+        "libhidlbase",
+        "libhidltransport",
+        "libhwbinder",
+        "libvintf",
+
+        "libhwui",
+        "libdl",
+    ],
+
+    local_include_dirs: ["android/graphics"],
+    export_include_dirs: [
+        ".",
+        "include",
+    ],
+    export_shared_lib_headers: [
+        // AndroidRuntime.h depends on nativehelper/jni.h
+        "libnativehelper",
+
+        // GraphicsJNI.h includes hwui headers
+        "libhwui",
+    ],
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
deleted file mode 100644
index 78aee3e..0000000
--- a/core/jni/Android.mk
+++ /dev/null
@@ -1,307 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS += -DHAVE_CONFIG_H -DKHTML_NO_EXCEPTIONS -DGKWQ_NO_JAVA
-LOCAL_CFLAGS += -DNO_SUPPORT_JS_BINDING -DQT_NO_WHEELEVENT -DKHTML_NO_XBL
-LOCAL_CFLAGS += -U__APPLE__
-LOCAL_CFLAGS += -Wno-unused-parameter
-LOCAL_CFLAGS += -Wno-non-virtual-dtor
-LOCAL_CFLAGS += -Wno-maybe-uninitialized -Wno-parentheses
-LOCAL_CFLAGS += -DHWUI_NEW_OPS
-LOCAL_CPPFLAGS += -Wno-conversion-null
-
-ifeq ($(TARGET_ARCH), arm)
-    LOCAL_CFLAGS += -DPACKED="__attribute__ ((packed))"
-else
-    LOCAL_CFLAGS += -DPACKED=""
-endif
-
-ifneq ($(ENABLE_CPUSETS),)
-    LOCAL_CFLAGS += -DENABLE_CPUSETS
-endif
-
-LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
-
-LOCAL_CFLAGS += -DU_USING_ICU_NAMESPACE=0
-
-LOCAL_SRC_FILES:= \
-    AndroidRuntime.cpp \
-    com_android_internal_content_NativeLibraryHelper.cpp \
-    com_google_android_gles_jni_EGLImpl.cpp \
-    com_google_android_gles_jni_GLImpl.cpp.arm \
-    android_app_Activity.cpp \
-    android_app_ApplicationLoaders.cpp \
-    android_app_NativeActivity.cpp \
-    android_app_admin_SecurityLog.cpp \
-    android_opengl_EGL14.cpp \
-    android_opengl_EGLExt.cpp \
-    android_opengl_GLES10.cpp \
-    android_opengl_GLES10Ext.cpp \
-    android_opengl_GLES11.cpp \
-    android_opengl_GLES11Ext.cpp \
-    android_opengl_GLES20.cpp \
-    android_opengl_GLES30.cpp \
-    android_opengl_GLES31.cpp \
-    android_opengl_GLES31Ext.cpp \
-    android_opengl_GLES32.cpp \
-    android_database_CursorWindow.cpp \
-    android_database_SQLiteCommon.cpp \
-    android_database_SQLiteConnection.cpp \
-    android_database_SQLiteGlobal.cpp \
-    android_database_SQLiteDebug.cpp \
-    android_graphics_drawable_AnimatedVectorDrawable.cpp \
-    android_graphics_drawable_VectorDrawable.cpp \
-    android_view_DisplayEventReceiver.cpp \
-    android_view_DisplayListCanvas.cpp \
-    android_view_GraphicBuffer.cpp \
-    android_view_HardwareLayer.cpp \
-    android_view_InputChannel.cpp \
-    android_view_InputDevice.cpp \
-    android_view_InputEventReceiver.cpp \
-    android_view_InputEventSender.cpp \
-    android_view_InputQueue.cpp \
-    android_view_KeyCharacterMap.cpp \
-    android_view_KeyEvent.cpp \
-    android_view_MotionEvent.cpp \
-    android_view_PointerIcon.cpp \
-    android_view_RenderNode.cpp \
-    android_view_RenderNodeAnimator.cpp \
-    android_view_Surface.cpp \
-    android_view_SurfaceControl.cpp \
-    android_view_SurfaceSession.cpp \
-    android_view_TextureView.cpp \
-    android_view_ThreadedRenderer.cpp \
-    android_view_VelocityTracker.cpp \
-    android_text_AndroidCharacter.cpp \
-    android_text_AndroidBidi.cpp \
-    android_text_StaticLayout.cpp \
-    android_os_Debug.cpp \
-    android_os_GraphicsEnvironment.cpp \
-    android_os_HwBinder.cpp \
-    android_os_HwBlob.cpp \
-    android_os_HwParcel.cpp \
-    android_os_HwRemoteBinder.cpp \
-    android_os_MemoryFile.cpp \
-    android_os_MessageQueue.cpp \
-    android_os_Parcel.cpp \
-    android_os_SELinux.cpp \
-    android_os_seccomp.cpp \
-    android_os_SystemClock.cpp \
-    android_os_SystemProperties.cpp \
-    android_os_Trace.cpp \
-    android_os_UEventObserver.cpp \
-    android_net_LocalSocketImpl.cpp \
-    android_net_NetUtils.cpp \
-    android_net_TrafficStats.cpp \
-    android_nio_utils.cpp \
-    android_util_AssetManager.cpp \
-    android_util_Binder.cpp \
-    android_util_EventLog.cpp \
-    android_util_MemoryIntArray.cpp \
-    android_util_Log.cpp \
-    android_util_PathParser.cpp \
-    android_util_Process.cpp \
-    android_util_StringBlock.cpp \
-    android_util_XmlBlock.cpp \
-    android_util_jar_StrictJarFile.cpp \
-    android_graphics_Canvas.cpp \
-    android_graphics_Picture.cpp \
-    android/graphics/Bitmap.cpp \
-    android/graphics/BitmapFactory.cpp \
-    android/graphics/Camera.cpp \
-    android/graphics/CanvasProperty.cpp \
-    android/graphics/ColorFilter.cpp \
-    android/graphics/DrawFilter.cpp \
-    android/graphics/FontFamily.cpp \
-    android/graphics/CreateJavaOutputStreamAdaptor.cpp \
-    android/graphics/Graphics.cpp \
-    android/graphics/HarfBuzzNGFaceSkia.cpp \
-    android/graphics/Interpolator.cpp \
-    android/graphics/MaskFilter.cpp \
-    android/graphics/Matrix.cpp \
-    android/graphics/Movie.cpp \
-    android/graphics/NinePatch.cpp \
-    android/graphics/NinePatchPeeker.cpp \
-    android/graphics/Paint.cpp \
-    android/graphics/Path.cpp \
-    android/graphics/PathMeasure.cpp \
-    android/graphics/PathEffect.cpp \
-    android/graphics/Picture.cpp \
-    android/graphics/PorterDuff.cpp \
-    android/graphics/BitmapRegionDecoder.cpp \
-    android/graphics/Rasterizer.cpp \
-    android/graphics/Region.cpp \
-    android/graphics/Shader.cpp \
-    android/graphics/SurfaceTexture.cpp \
-    android/graphics/Typeface.cpp \
-    android/graphics/Utils.cpp \
-    android/graphics/Xfermode.cpp \
-    android/graphics/YuvToJpegEncoder.cpp \
-    android/graphics/pdf/PdfDocument.cpp \
-    android/graphics/pdf/PdfEditor.cpp \
-    android/graphics/pdf/PdfRenderer.cpp \
-    android_media_AudioRecord.cpp \
-    android_media_AudioSystem.cpp \
-    android_media_AudioTrack.cpp \
-    android_media_DeviceCallback.cpp \
-    android_media_JetPlayer.cpp \
-    android_media_RemoteDisplay.cpp \
-    android_media_ToneGenerator.cpp \
-    android_hardware_Camera.cpp \
-    android_hardware_camera2_CameraMetadata.cpp \
-    android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
-    android_hardware_camera2_legacy_PerfMeasurement.cpp \
-    android_hardware_camera2_DngCreator.cpp \
-    android_hardware_Radio.cpp \
-    android_hardware_SensorManager.cpp \
-    android_hardware_SerialPort.cpp \
-    android_hardware_SoundTrigger.cpp \
-    android_hardware_UsbDevice.cpp \
-    android_hardware_UsbDeviceConnection.cpp \
-    android_hardware_UsbRequest.cpp \
-    android_hardware_location_ContextHubService.cpp \
-    android_hardware_location_ActivityRecognitionHardware.cpp \
-    android_util_FileObserver.cpp \
-    android/opengl/poly_clip.cpp.arm \
-    android/opengl/util.cpp \
-    android_server_NetworkManagementSocketTagger.cpp \
-    android_server_Watchdog.cpp \
-    android_ddm_DdmHandleNativeHeap.cpp \
-    android_backup_BackupDataInput.cpp \
-    android_backup_BackupDataOutput.cpp \
-    android_backup_FileBackupHelperBase.cpp \
-    android_backup_BackupHelperDispatcher.cpp \
-    android_app_backup_FullBackup.cpp \
-    android_content_res_ObbScanner.cpp \
-    android_content_res_Configuration.cpp \
-    android_animation_PropertyValuesHolder.cpp \
-    com_android_internal_net_NetworkStatsFactory.cpp \
-    com_android_internal_os_PathClassLoaderFactory.cpp \
-    com_android_internal_os_Zygote.cpp \
-    com_android_internal_util_VirtualRefBasePtr.cpp \
-    com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp \
-    hwbinder/EphemeralStorage.cpp \
-    fd_utils.cpp \
-
-LOCAL_C_INCLUDES += \
-    $(LOCAL_PATH)/include \
-    $(JNI_H_INCLUDE) \
-    $(LOCAL_PATH)/android/graphics \
-    $(LOCAL_PATH)/../../libs/hwui \
-    $(LOCAL_PATH)/../../../native/opengl/libs \
-    $(LOCAL_PATH)/../../../native/vulkan/include \
-    $(call include-path-for, bluedroid) \
-    $(call include-path-for, libhardware)/hardware \
-    $(call include-path-for, libhardware_legacy)/hardware_legacy \
-    $(TOP)/frameworks/base/media/jni \
-    $(TOP)/system/core/base/include \
-    $(TOP)/system/core/include \
-    $(TOP)/system/media/camera/include \
-    $(TOP)/system/netd/include \
-    external/pdfium/core/include/fpdfapi \
-    external/pdfium/fpdfsdk/include \
-    external/pdfium/public \
-    external/pdfium \
-    external/skia/include/private \
-    external/skia/src/core \
-    external/skia/src/effects \
-    external/skia/src/images \
-    external/sqlite/dist \
-    external/sqlite/android \
-    external/expat/lib \
-    external/tremor/Tremor \
-    external/harfbuzz_ng/src \
-    libcore/include \
-    $(call include-path-for, audio-utils) \
-    frameworks/minikin/include \
-    external/freetype/include
-# TODO: clean up Minikin so it doesn't need the freetype include
-
-LOCAL_STATIC_LIBRARIES := \
-    libseccomp_policy \
-    libselinux \
-    libcrypto \
-
-LOCAL_SHARED_LIBRARIES := \
-    libmemtrack \
-    libandroidfw \
-    libbase \
-    libexpat \
-    libnativehelper \
-    liblog \
-    libcutils \
-    libdebuggerd_client \
-    libutils \
-    libbinder \
-    libnetutils \
-    libui \
-    libgui \
-    libinput \
-    libinputflinger \
-    libcamera_client \
-    libcamera_metadata \
-    libskia \
-    libsqlite \
-    libEGL \
-    libGLESv1_CM \
-    libGLESv2 \
-    libvulkan \
-    libETC1 \
-    libhardware \
-    libhardware_legacy \
-    libselinux \
-    libsonivox \
-    libcrypto \
-    libssl \
-    libicuuc \
-    libicui18n \
-    libmedia \
-    libaudioclient \
-    libjpeg \
-    libusbhost \
-    libharfbuzz_ng \
-    libz \
-    libaudioutils \
-    libpdfium \
-    libimg_utils \
-    libnetd_client \
-    libradio \
-    libsoundtrigger \
-    libminikin \
-    libprocessgroup \
-    libnativebridge \
-    libradio_metadata \
-    libnativeloader \
-    libmemunreachable \
-    libhidlbase \
-    libhidltransport \
-    libhwbinder \
-    libvintf \
-
-LOCAL_SHARED_LIBRARIES += \
-    libhwui \
-    libdl
-
-# we need to access the private Bionic header
-# <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
-LOCAL_C_INCLUDES += bionic/libc/private
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-
-# AndroidRuntime.h depends on nativehelper/jni.h
-LOCAL_EXPORT_C_INCLUDE_DIRS += libnativehelper/include
-
-LOCAL_MODULE:= libandroid_runtime
-
-# -Wno-unknown-pragmas: necessary for Clang as the GL bindings need to turn
-#                       off a GCC warning that Clang doesn't know.
-LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code \
-        -Wno-unknown-pragmas
-
-# -Wno-c++11-extensions: Clang warns about Skia using the C++11 override keyword, but this project
-#                        is not being compiled with that level. Remove once this has changed.
-LOCAL_CLANG_CFLAGS += -Wno-c++11-extensions
-
-include $(BUILD_SHARED_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 3852758e..2b5c0d6 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -164,6 +164,8 @@
 extern int register_android_os_MessageQueue(JNIEnv* env);
 extern int register_android_os_Parcel(JNIEnv* env);
 extern int register_android_os_SELinux(JNIEnv* env);
+extern int register_android_os_VintfObject(JNIEnv *env);
+extern int register_android_os_VintfRuntimeInfo(JNIEnv *env);
 extern int register_android_os_seccomp(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv *env);
 extern int register_android_os_SystemClock(JNIEnv* env);
@@ -528,7 +530,7 @@
 /*
  * Reads a "property" into "buffer". If the property is non-empty, it
  * is treated as a dex2oat compiler option that should be
- * passed as a quoted option, e.g. "-Ximage-compiler-option --compiler-filter=verify-none".
+ * passed as a quoted option, e.g. "-Ximage-compiler-option --compiler-filter=assume-verified".
  *
  * The "compilerArg" is a prefix for the option such as "--compiler-filter=".
  *
@@ -604,6 +606,7 @@
 {
     JavaVMInitArgs initArgs;
     char propBuf[PROPERTY_VALUE_MAX];
+    char stackTraceDirBuf[sizeof("-Xstacktracedir:")-1 + PROPERTY_VALUE_MAX];
     char stackTraceFileBuf[sizeof("-Xstacktracefile:")-1 + PROPERTY_VALUE_MAX];
     char jniOptsBuf[sizeof("-Xjniopts:")-1 + PROPERTY_VALUE_MAX];
     char heapstartsizeOptsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
@@ -681,7 +684,12 @@
         executionMode = kEMJitCompiler;
     }
 
-    parseRuntimeOption("dalvik.vm.stack-trace-file", stackTraceFileBuf, "-Xstacktracefile:");
+    // If dalvik.vm.stack-trace-dir is set, it enables the "new" stack trace
+    // dump scheme and a new file is created for each stack dump. If it isn't set,
+    // the old scheme is enabled.
+    if (!parseRuntimeOption("dalvik.vm.stack-trace-dir", stackTraceDirBuf, "-Xstacktracedir:")) {
+        parseRuntimeOption("dalvik.vm.stack-trace-file", stackTraceFileBuf, "-Xstacktracefile:");
+    }
 
     strcpy(jniOptsBuf, "-Xjniopts:");
     if (parseRuntimeOption("dalvik.vm.jniopts", jniOptsBuf, "-Xjniopts:")) {
@@ -776,7 +784,7 @@
                                "-Xmx", "-Ximage-compiler-option");
     if (skip_compilation) {
         addOption("-Ximage-compiler-option");
-        addOption("--compiler-filter=verify-none");
+        addOption("--compiler-filter=assume-verified");
     } else {
         parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf,
                             "--compiler-filter=", "-Ximage-compiler-option");
@@ -807,7 +815,7 @@
                                "-Xmx", "-Xcompiler-option");
     if (skip_compilation) {
         addOption("-Xcompiler-option");
-        addOption("--compiler-filter=verify-none");
+        addOption("--compiler-filter=assume-verified");
 
         // We skip compilation when a minimal runtime is brought up for decryption. In that case
         // /data is temporarily backed by a tmpfs, which is usually small.
@@ -1306,6 +1314,8 @@
     REG_JNI(register_android_os_HwBlob),
     REG_JNI(register_android_os_HwParcel),
     REG_JNI(register_android_os_HwRemoteBinder),
+    REG_JNI(register_android_os_VintfObject),
+    REG_JNI(register_android_os_VintfRuntimeInfo),
     REG_JNI(register_android_nio_utils),
     REG_JNI(register_android_graphics_Canvas),
     REG_JNI(register_android_graphics_Graphics),
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
index 4b279f63..44a3555 100644
--- a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -22,7 +22,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 
-#include "activity_recognition.h"
+#include <hardware/activity_recognition.h>
 
 
 // keep base connection data from the HAL
diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp
index fbccfd5..baeda96 100644
--- a/core/jni/android_hardware_location_ContextHubService.cpp
+++ b/core/jni/android_hardware_location_ContextHubService.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "context_hub.h"
+#include <hardware/context_hub.h>
 
 #define LOG_NDEBUG 0
 #define LOG_TAG "ContextHubService"
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index a758489..68a08519 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -333,7 +333,9 @@
                             subHeap = HEAP_DALVIK_ZYGOTE;
                         } else if (strstr(name, "/dev/ashmem/dalvik-indirect ref") == name) {
                             subHeap = HEAP_DALVIK_INDIRECT_REFERENCE_TABLE;
-                        } else if (strstr(name, "/dev/ashmem/dalvik-jit-code-cache") == name) {
+                        } 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;
                         } else {
                             subHeap = HEAP_DALVIK_ACCOUNTING;  // Default to accounting.
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 678041f..b21ea828 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -574,7 +574,7 @@
     size_t parentHandle;
 
     const hidl_string *s;
-    status_t err = parcel->readBuffer(&parentHandle,
+    status_t err = parcel->readBuffer(sizeof(*s), &parentHandle,
             reinterpret_cast<const void**>(&s));
 
     if (err != OK) {
@@ -583,7 +583,7 @@
     }
 
     err = ::android::hardware::readEmbeddedFromParcel(
-            const_cast<hidl_string *>(s),
+            const_cast<hidl_string &>(*s),
             *parcel, parentHandle, 0 /* parentOffset */);
 
     if (err != OK) {
@@ -602,7 +602,7 @@
     size_t parentHandle;                                                       \
                                                                                \
     const hidl_vec<Type> *vec;                                                 \
-    status_t err = parcel->readBuffer(&parentHandle,                           \
+    status_t err = parcel->readBuffer(sizeof(*vec), &parentHandle,             \
             reinterpret_cast<const void**>(&vec));                             \
                                                                                \
     if (err != OK) {                                                           \
@@ -613,7 +613,7 @@
     size_t childHandle;                                                        \
                                                                                \
     err = ::android::hardware::readEmbeddedFromParcel(                         \
-                const_cast<hidl_vec<Type> *>(vec),                             \
+                const_cast<hidl_vec<Type> &>(*vec),                            \
                 *parcel,                                                       \
                 parentHandle,                                                  \
                 0 /* parentOffset */,                                          \
@@ -645,7 +645,7 @@
     size_t parentHandle;
 
     const hidl_vec<bool> *vec;
-    status_t err = parcel->readBuffer(&parentHandle,
+    status_t err = parcel->readBuffer(sizeof(*vec), &parentHandle,
             reinterpret_cast<const void**>(&vec));
 
     if (err != OK) {
@@ -656,7 +656,7 @@
     size_t childHandle;
 
     err = ::android::hardware::readEmbeddedFromParcel(
-                const_cast<hidl_vec<bool> *>(vec),
+                const_cast<hidl_vec<bool> &>(*vec),
                 *parcel,
                 parentHandle,
                 0 /* parentOffset */,
@@ -709,7 +709,7 @@
     size_t parentHandle;
 
     const string_vec *vec;
-    status_t err = parcel->readBuffer(&parentHandle,
+    status_t err = parcel->readBuffer(sizeof(*vec), &parentHandle,
             reinterpret_cast<const void **>(&vec));
 
     if (err != OK) {
@@ -719,16 +719,15 @@
 
     size_t childHandle;
     err = ::android::hardware::readEmbeddedFromParcel(
-            const_cast<string_vec *>(vec),
+            const_cast<string_vec &>(*vec),
             *parcel, parentHandle, 0 /* parentOffset */, &childHandle);
 
     for (size_t i = 0; (err == OK) && (i < vec->size()); ++i) {
         err = android::hardware::readEmbeddedFromParcel(
-                    const_cast<hidl_vec<hidl_string> *>(vec),
+                    const_cast<hidl_string &>((*vec)[i]),
                     *parcel,
                     childHandle,
-                    i * sizeof(hidl_string),
-                    nullptr /* childHandle */);
+                    i * sizeof(hidl_string) /* parentOffset */);
     }
 
     if (err != OK) {
@@ -810,13 +809,20 @@
     return JHwRemoteBinder::NewObject(env, binder);
 }
 
-static jobject JHwParcel_native_readBuffer(JNIEnv *env, jobject thiz) {
+static jobject JHwParcel_native_readBuffer(JNIEnv *env, jobject thiz,
+                                           jlong expectedSize) {
     hardware::Parcel *parcel =
         JHwParcel::GetNativeContext(env, thiz)->getParcel();
 
     size_t handle;
     const void *ptr;
-    status_t status = parcel->readBuffer(&handle, &ptr);
+
+    if (expectedSize < 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return nullptr;
+    }
+
+    status_t status = parcel->readBuffer(expectedSize, &handle, &ptr);
 
     if (status != OK) {
         jniThrowException(env, "java/util/NoSuchElementException", NULL);
@@ -827,8 +833,8 @@
 }
 
 static jobject JHwParcel_native_readEmbeddedBuffer(
-        JNIEnv *env, jobject thiz, jlong parentHandle, jlong offset,
-        jboolean nullable) {
+        JNIEnv *env, jobject thiz, jlong expectedSize,
+        jlong parentHandle, jlong offset, jboolean nullable) {
     hardware::Parcel *parcel =
         JHwParcel::GetNativeContext(env, thiz)->getParcel();
 
@@ -836,8 +842,13 @@
 
     const void *ptr;
     status_t status =
-        parcel->readNullableEmbeddedBuffer(&childHandle, parentHandle, offset,
-                &ptr);
+        parcel->readNullableEmbeddedBuffer(expectedSize,
+                &childHandle, parentHandle, offset, &ptr);
+
+    if (expectedSize < 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return nullptr;
+    }
 
     if (status != OK) {
         jniThrowException(env, "java/util/NoSuchElementException", NULL);
@@ -952,10 +963,10 @@
 
     { "send", "()V", (void *)JHwParcel_native_send },
 
-    { "readBuffer", "()L" PACKAGE_PATH "/HwBlob;",
+    { "readBuffer", "(J)L" PACKAGE_PATH "/HwBlob;",
         (void *)JHwParcel_native_readBuffer },
 
-    { "readEmbeddedBuffer", "(JJZ)L" PACKAGE_PATH "/HwBlob;",
+    { "readEmbeddedBuffer", "(JJJZ)L" PACKAGE_PATH "/HwBlob;",
         (void *)JHwParcel_native_readEmbeddedBuffer },
 
     { "writeBuffer", "(L" PACKAGE_PATH "/HwBlob;)V",
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
new file mode 100644
index 0000000..fa9379e
--- /dev/null
+++ b/core/jni/android_os_VintfObject.cpp
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VintfObject"
+//#define LOG_NDEBUG 0
+#include <android-base/logging.h>
+
+#include <vector>
+#include <string>
+
+#include <JNIHelp.h>
+#include <vintf/VintfObject.h>
+#include <vintf/parse_string.h>
+#include <vintf/parse_xml.h>
+
+#include "core_jni_helpers.h"
+
+static jclass gString;
+static jclass gHashMapClazz;
+static jmethodID gHashMapInit;
+static jmethodID gHashMapPut;
+
+namespace android {
+
+using vintf::HalManifest;
+using vintf::SchemaType;
+using vintf::VintfObject;
+using vintf::XmlConverter;
+using vintf::Vndk;
+using vintf::gHalManifestConverter;
+using vintf::gCompatibilityMatrixConverter;
+using vintf::to_string;
+
+template<typename V>
+static inline jobjectArray toJavaStringArray(JNIEnv* env, const V& v) {
+    size_t i;
+    typename V::const_iterator it;
+    jobjectArray ret = env->NewObjectArray(v.size(), gString, NULL /* init element */);
+    for (i = 0, it = v.begin(); it != v.end(); ++i, ++it) {
+        env->SetObjectArrayElement(ret, i, env->NewStringUTF(it->c_str()));
+    }
+    return ret;
+}
+
+template<typename T>
+static void tryAddSchema(const T* object, const XmlConverter<T>& converter,
+        const std::string& description,
+        std::vector<std::string>* cStrings) {
+    if (object == nullptr) {
+        LOG(WARNING) << __FUNCTION__ << "Cannot get " << description;
+    } else {
+        cStrings->push_back(converter(*object));
+    }
+}
+
+static void tryAddHalNamesAndVersions(const HalManifest *manifest,
+        const std::string& description,
+        std::set<std::string> *output) {
+    if (manifest == nullptr) {
+        LOG(WARNING) << __FUNCTION__ << "Cannot get " << description;
+    } else {
+        auto names = manifest->getHalNamesAndVersions();
+        output->insert(names.begin(), names.end());
+    }
+}
+
+static jobjectArray android_os_VintfObject_report(JNIEnv* env, jclass)
+{
+    std::vector<std::string> cStrings;
+
+    tryAddSchema(VintfObject::GetDeviceHalManifest(), gHalManifestConverter,
+            "device manifest", &cStrings);
+    tryAddSchema(VintfObject::GetFrameworkHalManifest(), gHalManifestConverter,
+            "framework manifest", &cStrings);
+    tryAddSchema(VintfObject::GetDeviceCompatibilityMatrix(), gCompatibilityMatrixConverter,
+            "device compatibility matrix", &cStrings);
+    tryAddSchema(VintfObject::GetFrameworkCompatibilityMatrix(), gCompatibilityMatrixConverter,
+            "framework compatibility matrix", &cStrings);
+
+    return toJavaStringArray(env, cStrings);
+}
+
+static jint android_os_VintfObject_verify(JNIEnv* env, jclass, jobjectArray packageInfo) {
+    size_t count = env->GetArrayLength(packageInfo);
+    std::vector<std::string> cPackageInfo{count};
+    for (size_t i = 0; i < count; ++i) {
+        jstring element = (jstring)env->GetObjectArrayElement(packageInfo, i);
+        const char *cString = env->GetStringUTFChars(element, NULL /* isCopy */);
+        cPackageInfo[i] = cString;
+        env->ReleaseStringUTFChars(element, cString);
+    }
+    int32_t status = VintfObject::CheckCompatibility(cPackageInfo);
+    return status;
+}
+
+static jobjectArray android_os_VintfObject_getHalNamesAndVersions(JNIEnv* env, jclass) {
+    std::set<std::string> halNames;
+    tryAddHalNamesAndVersions(VintfObject::GetDeviceHalManifest(),
+            "device manifest", &halNames);
+    tryAddHalNamesAndVersions(VintfObject::GetFrameworkHalManifest(),
+            "framework manifest", &halNames);
+    return toJavaStringArray(env, halNames);
+}
+
+static jstring android_os_VintfObject_getSepolicyVersion(JNIEnv* env, jclass) {
+    const HalManifest *manifest = VintfObject::GetDeviceHalManifest();
+    if (manifest == nullptr || manifest->type() != SchemaType::DEVICE) {
+        LOG(WARNING) << __FUNCTION__ << "Cannot get device manifest";
+        return nullptr;
+    }
+    std::string cString = to_string(manifest->sepolicyVersion());
+    return env->NewStringUTF(cString.c_str());
+}
+
+static jobject android_os_VintfObject_getVndkSnapshots(JNIEnv* env, jclass) {
+    const HalManifest *manifest = VintfObject::GetFrameworkHalManifest();
+    if (manifest == nullptr || manifest->type() != SchemaType::FRAMEWORK) {
+        LOG(WARNING) << __FUNCTION__ << "Cannot get framework manifest";
+        return nullptr;
+    }
+    jobject jMap = env->NewObject(gHashMapClazz, gHashMapInit);
+    for (const Vndk &vndk : manifest->vndks()) {
+        std::string key = to_string(vndk.versionRange());
+        env->CallObjectMethod(jMap, gHashMapPut,
+                env->NewStringUTF(key.c_str()), toJavaStringArray(env, vndk.libraries()));
+    }
+    return jMap;
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gVintfObjectMethods[] = {
+    {"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
+    {"verify", "([Ljava/lang/String;)I", (void*)android_os_VintfObject_verify},
+    {"getHalNamesAndVersions", "()[Ljava/lang/String;", (void*)android_os_VintfObject_getHalNamesAndVersions},
+    {"getSepolicyVersion", "()Ljava/lang/String;", (void*)android_os_VintfObject_getSepolicyVersion},
+    {"getVndkSnapshots", "()Ljava/util/Map;", (void*)android_os_VintfObject_getVndkSnapshots},
+};
+
+const char* const kVintfObjectPathName = "android/os/VintfObject";
+
+int register_android_os_VintfObject(JNIEnv* env)
+{
+
+    gString = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/String"));
+    gHashMapClazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/util/HashMap"));
+    gHashMapInit = GetMethodIDOrDie(env, gHashMapClazz, "<init>", "()V");
+    gHashMapPut = GetMethodIDOrDie(env, gHashMapClazz,
+            "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+
+    return RegisterMethodsOrDie(env, kVintfObjectPathName, gVintfObjectMethods,
+            NELEM(gVintfObjectMethods));
+}
+
+};
diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp
new file mode 100644
index 0000000..ecb6854
--- /dev/null
+++ b/core/jni/android_os_VintfRuntimeInfo.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VintfRuntimeInfo"
+//#define LOG_NDEBUG 0
+
+#include <JNIHelp.h>
+#include <vintf/VintfObject.h>
+#include <vintf/parse_string.h>
+#include <vintf/parse_xml.h>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+using vintf::RuntimeInfo;
+using vintf::VintfObject;
+
+#define MAP_STRING_METHOD(javaMethod, cppString)                                       \
+    static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) \
+    {                                                                                  \
+        const RuntimeInfo *info = VintfObject::GetRuntimeInfo();                       \
+        if (info == nullptr) return nullptr;                                           \
+        return env->NewStringUTF((cppString).c_str());                                 \
+    }                                                                                  \
+
+MAP_STRING_METHOD(getCpuInfo, info->cpuInfo());
+MAP_STRING_METHOD(getOsName, info->osName());
+MAP_STRING_METHOD(getNodeName, info->nodeName());
+MAP_STRING_METHOD(getOsRelease, info->osRelease());
+MAP_STRING_METHOD(getOsVersion, info->osVersion());
+MAP_STRING_METHOD(getHardwareId, info->hardwareId());
+MAP_STRING_METHOD(getKernelVersion, vintf::to_string(info->kernelVersion()));
+MAP_STRING_METHOD(getBootAvbVersion, vintf::to_string(info->bootAvbVersion()));
+MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbVersion()));
+
+
+static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv *env, jclass clazz)
+{
+    const RuntimeInfo *info = VintfObject::GetRuntimeInfo();
+    if (info == nullptr) return 0;
+    return static_cast<jlong>(info->kernelSepolicyVersion());
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gVintfRuntimeInfoMethods[] = {
+    {"getKernelSepolicyVersion", "()J", (void*)android_os_VintfRuntimeInfo_getKernelSepolicyVersion},
+    {"getCpuInfo", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getCpuInfo},
+    {"getOsName", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getOsName},
+    {"getNodeName", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getNodeName},
+    {"getOsRelease", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getOsRelease},
+    {"getOsVersion", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getOsVersion},
+    {"getHardwareId", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getHardwareId},
+    {"getKernelVersion", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getKernelVersion},
+    {"getBootAvbVersion", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getBootAvbVersion},
+    {"getBootVbmetaAvbVersion", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getBootVbmetaAvbVersion},
+};
+
+const char* const kVintfRuntimeInfoPathName = "android/os/VintfRuntimeInfo";
+
+int register_android_os_VintfRuntimeInfo(JNIEnv* env)
+{
+    return RegisterMethodsOrDie(env, kVintfRuntimeInfoPathName, gVintfRuntimeInfoMethods, NELEM(gVintfRuntimeInfoMethods));
+}
+
+};
diff --git a/core/jni/android_util_Log.cpp b/core/jni/android_util_Log.cpp
index 20dfe78..56505af 100644
--- a/core/jni/android_util_Log.cpp
+++ b/core/jni/android_util_Log.cpp
@@ -58,16 +58,7 @@
         return false;
     }
 
-    jboolean result = false;
-    if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
-        char buf2[200];
-        snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %zu characters\n",
-                chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));
-
-        jniThrowException(env, "java/lang/IllegalArgumentException", buf2);
-    } else {
-        result = isLoggable(chars, level);
-    }
+    jboolean result = isLoggable(chars, level);
 
     env->ReleaseStringUTFChars(tag, chars);
     return result;
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index b95258b..d73e7dd 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -27,6 +27,7 @@
 #include <fcntl.h>
 #include <grp.h>
 #include <inttypes.h>
+#include <malloc.h>
 #include <mntent.h>
 #include <paths.h>
 #include <signal.h>
@@ -519,6 +520,9 @@
     // The child process.
     gMallocLeakZygoteChild = 1;
 
+    // Set the jemalloc decay time to 1.
+    mallopt(M_DECAY_TIME, 1);
+
     // Clean up any descriptors which must be closed immediately
     DetachDescriptors(env, fdsToClose);
 
@@ -685,11 +689,14 @@
 
     // Grant CAP_WAKE_ALARM to the Bluetooth process.
     // Additionally, allow bluetooth to open packet sockets so it can start the DHCP client.
+    // Grant CAP_SYS_NICE to allow Bluetooth to set RT priority for
+    // audio-related threads.
     // TODO: consider making such functionality an RPC to netd.
     if (multiuser_get_app_id(uid) == AID_BLUETOOTH) {
       capabilities |= (1LL << CAP_WAKE_ALARM);
       capabilities |= (1LL << CAP_NET_RAW);
       capabilities |= (1LL << CAP_NET_BIND_SERVICE);
+      capabilities |= (1LL << CAP_SYS_NICE);
     }
 
     // Grant CAP_BLOCK_SUSPEND to processes that belong to GID "wakelock"
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9991a20..c2f27a9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -122,6 +122,7 @@
     <protected-broadcast android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />
     <protected-broadcast android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
     <protected-broadcast android:name="android.bluetooth.adapter.action.LOCAL_NAME_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.UUID" />
     <protected-broadcast android:name="android.bluetooth.device.action.MAS_INSTANCE" />
@@ -787,16 +788,6 @@
         android:description="@string/permdesc_callPhone"
         android:protectionLevel="dangerous" />
 
-    <!-- Allows an application to manage its own calls, but rely on the system to route focus to the
-         currently active call.
-        <p>Protection level: dangerous
-    -->
-    <permission android:name="android.permission.MANAGE_OWN_CALLS"
-        android:permissionGroup="android.permission-group.PHONE"
-        android:label="@string/permlab_manageOwnCalls"
-        android:description="@string/permdesc_manageOwnCalls"
-        android:protectionLevel="dangerous" />
-
     <!-- Allows an application to access the IMS call service: making and
          modifying a call
         <p>Protection level: signature|privileged
@@ -874,6 +865,17 @@
         android:description="@string/permdesc_processOutgoingCalls"
         android:protectionLevel="dangerous" />
 
+    <!-- Allows a calling application which manages it own calls through the self-managed
+         {@link android.telecom.ConnectionService} APIs.  See
+         {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED for more information on the
+         self-managed ConnectionService APIs.
+         <p>Protection level: normal
+    -->
+    <permission android:name="android.permission.MANAGE_OWN_CALLS"
+                android:label="@string/permlab_manageOwnCalls"
+                android:description="@string/permdesc_manageOwnCalls"
+                android:protectionLevel="normal" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device microphone                        -->
     <!-- ====================================================================== -->
@@ -1559,6 +1561,14 @@
     <permission android:name="android.permission.BIND_INCALL_SERVICE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Must be required by a link {@link android.telephony.VisualVoicemailService} to ensure that
+         only the system can bind to it.
+         <p>Protection level: signature|privileged
+    -->
+    <permission
+      android:name="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
+      android:protectionLevel="signature|privileged"/>
+
     <!-- Must be required by a {@link android.telecom.CallScreeningService},
          to ensure that only the system can bind to it.
          <p>Protection level: signature|privileged
@@ -2084,6 +2094,22 @@
     <permission android:name="android.permission.UPDATE_CONFIG"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows a time zone rule updater application to request
+         the system installs / uninstalls timezone rules.
+         <p>An application requesting this permission is responsible for
+         verifying the source and integrity of the update before passing
+         it off to the installer components.
+         @hide -->
+    <permission android:name="android.permission.UPDATE_TIME_ZONE_RULES"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- Must be required by a time zone rule updater application,
+         to ensure that only the system can trigger it.
+         @hide -->
+    <permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"
+        android:protectionLevel="signature" />
+    <uses-permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"/>
+
     <!-- Allows the system to reset throttling in shortcut manager.
          @hide -->
     <permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING"
diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml
index 10166a5..0cafed6 100755
--- a/core/res/res/values-mcc311-mnc480/config.xml
+++ b/core/res/res/values-mcc311-mnc480/config.xml
@@ -48,9 +48,6 @@
          provisioning, availability etc -->
     <bool name="config_carrier_vt_available">true</bool>
 
-    <!-- Flag specifying whether VoLTE availability is based on provisioning -->
-    <bool name="config_carrier_volte_provisioned">true</bool>
-
     <bool name="config_auto_attach_data_on_creation">false</bool>
 
     <!--Thresholds for LTE dbm in status bar-->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 589aa07..37d03ad 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1282,6 +1282,49 @@
     <!-- True if WallpaperService is enabled -->
     <bool name="config_enableWallpaperService">true</bool>
 
+    <!-- Enables the TimeZoneRuleManager service. This is the master switch for the updateable time
+         zone update mechanism. -->
+    <bool name="config_enableUpdateableTimeZoneRules">false</bool>
+
+    <!-- Enables APK-based time zone update triggering. Set this to false when updates are triggered
+         via external events and not by APK updates. For example, if an updater checks with a server
+         on a regular schedule.
+         [This is only used if config_enableUpdateableTimeZoneRules is true.] -->
+    <bool name="config_timeZoneRulesUpdateTrackingEnabled">false</bool>
+
+    <!-- The package of the time zone rules updater application. Expected to be the same
+         for all Android devices that support APK-based time zone rule updates.
+         A package-targeted android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
+         will be sent to the updater app if the system server detects an update to the updater or
+         data app packages.
+         The package referenced here must have the android.permission.UPDATE_TIME_ZONE_RULES
+         permission.
+         [This is only used if config_enableUpdateableTimeZoneRules and
+         config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+    <string name="config_timeZoneRulesUpdaterPackage" translateable="false"></string>
+
+    <!-- The package of the time zone rules data application. Expected to be configured
+         by OEMs to reference their own priv-app APK package.
+         A package-targeted android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
+         will be sent to the updater app if the system server detects an update to the updater or
+         data app packages.
+         [This is only used if config_enableUpdateableTimeZoneRules and
+         config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+    <string name="config_timeZoneRulesDataPackage" translateable="false"></string>
+
+    <!-- The allowed time in milliseconds between an update check intent being broadcast and the
+         response being considered overdue. Reliability triggers will not fire in this time.
+         [This is only used if config_enableUpdateableTimeZoneRules and
+         config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+    <!-- 5 minutes -->
+    <integer name="config_timeZoneRulesCheckTimeMillisAllowed">300000</integer>
+
+    <!-- The number of times a time zone update check is allowed to fail before the system will stop
+         reacting to reliability triggers.
+         [This is only used if config_enableUpdateableTimeZoneRules and
+         config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+    <integer name="config_timeZoneRulesCheckRetryCount">5</integer>
+
     <!-- Whether to enable network location overlay which allows network
          location provider to be replaced by an app at run-time. When disabled,
          only the config_networkLocationProviderPackageName package will be
@@ -2285,9 +2328,6 @@
          provisioning, availability etc -->
     <bool name="config_carrier_volte_available">false</bool>
 
-    <!-- Flag specifying whether VoLTE availability is based on provisioning -->
-    <bool name="config_carrier_volte_provisioned">false</bool>
-
     <!-- Flag specifying whether VoLTE TTY is supported -->
     <bool name="config_carrier_volte_tty_supported">true</bool>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d49345d..0514267 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2979,6 +2979,8 @@
 
     <!-- Do not translate. Default access point SSID used for tethering -->
     <string name="wifi_tether_configure_ssid_default" translatable="false">AndroidAP</string>
+    <!-- Do not translate. Default access point SSID used for local only hotspot -->
+    <string name="wifi_localhotspot_configure_ssid_default" translatable="false">AndroidShare</string>
 
     <!-- A notification is shown the first time a connection is attempted on an app owned AP -->
     <!-- title for this message -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4db748e..f3ce719 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -275,6 +275,12 @@
   <java-symbol type="bool" name="split_action_bar_is_narrow" />
   <java-symbol type="bool" name="config_useVolumeKeySounds" />
   <java-symbol type="bool" name="config_enableWallpaperService" />
+  <java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" />
+  <java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" />
+  <java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" />
+  <java-symbol type="string" name="config_timeZoneRulesDataPackage" />
+  <java-symbol type="integer" name="config_timeZoneRulesCheckTimeMillisAllowed" />
+  <java-symbol type="integer" name="config_timeZoneRulesCheckRetryCount" />
   <java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
   <java-symbol type="bool" name="config_enableScreenshotChord" />
   <java-symbol type="bool" name="config_bluetooth_default_profiles" />
@@ -981,6 +987,7 @@
   <java-symbol type="string" name="wifi_p2p_turnon_message" />
   <java-symbol type="string" name="wifi_p2p_frequency_conflict_message" />
   <java-symbol type="string" name="wifi_tether_configure_ssid_default" />
+  <java-symbol type="string" name="wifi_localhotspot_configure_ssid_default" />
   <java-symbol type="string" name="wifi_watchdog_network_disabled" />
   <java-symbol type="string" name="wifi_watchdog_network_disabled_detailed" />
   <java-symbol type="string" name="imei" />
@@ -2255,7 +2262,6 @@
   <java-symbol type="bool" name="imsServiceAllowTurnOff" />
   <java-symbol type="bool" name="config_device_volte_available" />
   <java-symbol type="bool" name="config_carrier_volte_available" />
-  <java-symbol type="bool" name="config_carrier_volte_provisioned" />
   <java-symbol type="bool" name="config_carrier_volte_tty_supported" />
   <java-symbol type="bool" name="config_device_vt_available" />
   <java-symbol type="bool" name="config_device_respects_hold_carrier_config" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 1ae922a..644638d 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -55,7 +55,7 @@
     <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
 
     <!-- Bulgaria: 4-5 digits, plus EU -->
-    <shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}" />
+    <shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}|1988|1490" />
 
     <!-- Bahrain: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="bh" pattern="\\d{1,5}" free="81181" />
@@ -95,7 +95,7 @@
 
     <!-- Spain: 5-6 digits: 25xxx, 27xxx, 280xx, 35xxx, 37xxx, 795xxx, 797xxx, 995xxx, 997xxx, plus EU.
          http://www.legallink.es/?q=en/content/which-current-regulatory-status-premium-rate-services-spain -->
-    <shortcode country="es" premium="[23][57]\\d{3}|280\\d{2}|[79]9[57]\\d{3}" free="116\\d{3}|22791|222145" />
+    <shortcode country="es" premium="[23][57]\\d{3}|280\\d{2}|[79]9[57]\\d{3}" free="116\\d{3}|22791|222145|22189" />
 
     <!-- Finland: 5-6 digits, premium 0600, 0700: http://en.wikipedia.org/wiki/Telephone_numbers_in_Finland -->
     <shortcode country="fi" pattern="\\d{5,6}" premium="0600.*|0700.*|171(?:59|63)" free="116\\d{3}|14789" />
@@ -103,12 +103,12 @@
     <!-- France: 5 digits, free: 3xxxx, premium [4-8]xxxx, plus EU:
          http://clients.txtnation.com/entries/161972-france-premium-sms-short-code-requirements,
          visual voicemail code for Orange: 21101 -->
-    <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101" />
+    <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366" />
 
     <!-- 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" />
+    <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" />
 
     <!-- Georgia: 4 digits, known premium codes listed -->
     <shortcode country="ge" pattern="\\d{4}" premium="801[234]|888[239]" />
@@ -116,12 +116,15 @@
     <!-- 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" />
 
+    <!-- Croatia -->
+    <shortcode country="hr" free="13062" />
+
     <!-- Hungary: 4 or 10 digits starting with 1 or 0, plus EU:
          http://clients.txtnation.com/entries/209633-hungary-premium-sms-short-code-regulations -->
     <shortcode country="hu" pattern="[01](?:\\d{3}|\\d{9})" premium="0691227910|1784" free="116\\d{3}" />
 
     <!-- India: 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="in" pattern="\\d{1,5}" free="59336" />
+    <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
 
     <!-- Indonesia: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645" />
@@ -153,45 +156,48 @@
     <shortcode country="kw" pattern="\\d{1,5}" free="1378|50420|94006" />
 
     <!-- Lithuania: 3-5 digits, known premium codes listed, plus EU -->
-    <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}" />
+    <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}|1399" />
 
     <!-- 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}" />
+    <shortcode country="lu" premium="6\\d{4}" free="116\\d{3}|60231" />
 
     <!-- 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}" />
+    <shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}|1399" />
 
     <!-- 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" />
 
     <!-- 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" />
+    <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288" />
 
     <!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
-    <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225" />
+    <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223" />
 
     <!-- Norway: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" />
 
     <!-- New Zealand: 3-4 digits, known premium codes listed -->
-    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" />
+    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="3067|3068|4053" />
 
     <!-- Philippines -->
     <shortcode country="ph" free="2147|5495|5496" />
 
+    <!-- Pakistan -->
+    <shortcode country="pk" free="2057" />
+
     <!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
 
     <!-- Portugal: 5 digits, plus EU:
          http://clients.txtnation.com/entries/158326-portugal-premium-sms-short-code-regulations -->
-    <shortcode country="pt" premium="6[1289]\\d{3}" free="116\\d{3}" />
+    <shortcode country="pt" premium="6[1289]\\d{3}" free="116\\d{3}|1262" />
 
     <!-- Qatar: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="qa" pattern="\\d{1,5}" free="92451" />
 
     <!-- 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" />
+    <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" />
@@ -207,7 +213,7 @@
     <shortcode country="sg" pattern="7\\d{4}" premium="73800" standard="74688|70134" />
 
     <!-- Slovenia: 4 digits (premium=3xxx, 6xxx, 8xxx), plus EU: http://www.cmtelecom.com/premium-sms/slovenia -->
-    <shortcode country="si" pattern="\\d{4}" premium="[368]\\d{3}" free="116\\d{3}" />
+    <shortcode country="si" pattern="\\d{4}" premium="[368]\\d{3}" free="116\\d{3}|3133" />
 
     <!-- Slovakia: 4 digits (premium), plus EU: http://www.cmtelecom.com/premium-sms/slovakia -->
     <shortcode country="sk" premium="\\d{4}" free="116\\d{3}|8000" />
@@ -219,7 +225,7 @@
     <shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" />
 
     <!-- Turkey -->
-    <shortcode country="tr" free="7529|5528" />
+    <shortcode country="tr" free="7529|5528|6493" />
 
     <!-- Ukraine: 4 digits, known premium codes listed -->
     <shortcode country="ua" pattern="\\d{4}" premium="444[3-9]|70[579]4|7540" />
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java
index 411a3f8..37b2a50 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java
@@ -72,8 +72,6 @@
             getAddress();
         } else if ("getBondedDevices".equals(command)) {
             getBondedDevices();
-        } else if ("enableBtSnoop".equals(command)) {
-            enableBtSnoop();
         } else {
             finish(null);
         }
@@ -116,12 +114,6 @@
         finish(mSuccessResult);
     }
 
-    public void enableBtSnoop() {
-        Assert.assertTrue("failed to enable snoop log",
-                getBluetoothAdapter().configHciSnoopLog(true));
-        finish(mSuccessResult);
-    }
-
     public void finish(Bundle result) {
         if (result == null) {
             result = new Bundle();
diff --git a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
new file mode 100644
index 0000000..9bbcd3d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.app.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link DistroFormatVersion}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class DistroFormatVersionTest {
+
+    @Test
+    public void equalsAndHashCode() {
+        DistroFormatVersion one = new DistroFormatVersion(1, 2);
+        assertEqualsContract(one, one);
+
+        DistroFormatVersion two = new DistroFormatVersion(1, 2);
+        assertEqualsContract(one, two);
+
+        DistroFormatVersion three = new DistroFormatVersion(2, 1);
+        assertFalse(one.equals(three));
+    }
+
+    @Test
+    public void parcelable() {
+        DistroFormatVersion version = new DistroFormatVersion(2, 3);
+
+        Parcel parcel = Parcel.obtain();
+        version.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+
+        DistroFormatVersion newVersion = DistroFormatVersion.CREATOR.createFromParcel(parcel);
+
+        assertEquals(version, newVersion);
+    }
+
+    @Test
+    public void supportsVersion() {
+        DistroFormatVersion deviceVersion = new DistroFormatVersion(2, 2);
+        assertTrue(deviceVersion.supports(deviceVersion));
+
+        DistroFormatVersion sameVersion = new DistroFormatVersion(2, 2);
+        assertTrue(deviceVersion.supports(sameVersion));
+
+        // Minor versions are backwards compatible.
+        DistroFormatVersion sameMajorNewerMinor = new DistroFormatVersion(2, 3);
+        assertTrue(deviceVersion.supports(sameMajorNewerMinor));
+        DistroFormatVersion sameMajorOlderMinor = new DistroFormatVersion(2, 1);
+        assertFalse(deviceVersion.supports(sameMajorOlderMinor));
+
+        // Major versions are not backwards compatible.
+        DistroFormatVersion newerMajor = new DistroFormatVersion(1, 2);
+        assertFalse(deviceVersion.supports(newerMajor));
+        DistroFormatVersion olderMajor = new DistroFormatVersion(3, 2);
+        assertFalse(deviceVersion.supports(olderMajor));
+    }
+
+    private static void assertEqualsContract(DistroFormatVersion one, DistroFormatVersion two) {
+        assertEquals(one, two);
+        assertEquals(one.hashCode(), two.hashCode());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
new file mode 100644
index 0000000..2fbc9a1
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.app.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link DistroRulesVersion}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class DistroRulesVersionTest {
+
+    @Test
+    public void equalsAndHashCode() {
+        DistroRulesVersion one = new DistroRulesVersion("2016a", 2);
+        assertEqualsContract(one, one);
+
+        DistroRulesVersion two = new DistroRulesVersion("2016a", 2);
+        assertEqualsContract(one, two);
+
+        DistroRulesVersion three = new DistroRulesVersion("2016b", 1);
+        assertFalse(one.equals(three));
+    }
+
+    @Test
+    public void parcelable() {
+        DistroRulesVersion version = new DistroRulesVersion("2016a", 2);
+
+        Parcel parcel = Parcel.obtain();
+        version.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+
+        DistroRulesVersion newVersion = DistroRulesVersion.CREATOR.createFromParcel(parcel);
+
+        assertEquals(version, newVersion);
+    }
+
+    @Test
+    public void isOlderThan() {
+        DistroRulesVersion deviceVersion = new DistroRulesVersion("2016b", 2);
+        assertFalse(deviceVersion.isOlderThan(deviceVersion));
+
+        DistroRulesVersion sameVersion = new DistroRulesVersion("2016b", 2);
+        assertFalse(deviceVersion.isOlderThan(sameVersion));
+
+        DistroRulesVersion sameRulesNewerRevision = new DistroRulesVersion("2016b", 3);
+        assertTrue(deviceVersion.isOlderThan(sameRulesNewerRevision));
+
+        DistroRulesVersion sameRulesOlderRevision = new DistroRulesVersion("2016b", 1);
+        assertFalse(deviceVersion.isOlderThan(sameRulesOlderRevision));
+
+        DistroRulesVersion newerRules = new DistroRulesVersion("2016c", 2);
+        assertTrue(deviceVersion.isOlderThan(newerRules));
+
+        DistroRulesVersion olderRules = new DistroRulesVersion("2016a", 2);
+        assertFalse(deviceVersion.isOlderThan(olderRules));
+    }
+
+    private static void assertEqualsContract(DistroRulesVersion one, DistroRulesVersion two) {
+        assertEquals(one, two);
+        assertEquals(one.hashCode(), two.hashCode());
+    }
+}
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
new file mode 100644
index 0000000..a9357c9
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.app.timezone;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link RulesState}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class RulesStateTest {
+
+    @Test
+    public void equalsAndHashCode() {
+        RulesState one = new RulesState(
+                "2016a", formatVersion(1, 2), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+        assertEqualsContract(one, one);
+
+        RulesState two = new RulesState(
+                "2016a", formatVersion(1, 2), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+        assertEqualsContract(one, two);
+
+        RulesState differentSystemRules = new RulesState(
+                "2016b", formatVersion(1, 2), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+        assertFalse(one.equals(differentSystemRules));
+
+        RulesState differentFormatVersion = new RulesState(
+                "2016a", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+        assertFalse(one.equals(differentFormatVersion));
+
+        RulesState differentOperationInProgress = new RulesState(
+                "2016a", formatVersion(1, 1), true /* operationInProgress */,
+                RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+        assertFalse(one.equals(differentOperationInProgress));
+
+        RulesState differentStagedOperation = new RulesState(
+                "2016a", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+        assertFalse(one.equals(differentStagedOperation));
+
+        RulesState differentStagedInstallVersion = new RulesState(
+                "2016a", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 4),
+                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+        assertFalse(one.equals(differentStagedInstallVersion));
+
+        RulesState differentInstalled = new RulesState(
+                "2016a", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+                RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+        assertFalse(one.equals(differentInstalled));
+
+        RulesState differentInstalledVersion = new RulesState(
+                "2016a", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
+        assertFalse(one.equals(differentInstalledVersion));
+    }
+
+    @Test
+    public void parcelable() {
+        RulesState rulesState1 = new RulesState(
+                "2016a", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016b", 2),
+                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
+        checkParcelableRoundTrip(rulesState1);
+
+        RulesState rulesStateWithNulls = new RulesState(
+                "2016a", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+        checkParcelableRoundTrip(rulesStateWithNulls);
+
+        RulesState rulesStateWithUnknowns = new RulesState(
+                "2016a", formatVersion(1, 1), true /* operationInProgress */,
+                RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+        checkParcelableRoundTrip(rulesStateWithNulls);
+    }
+
+    private static void checkParcelableRoundTrip(RulesState rulesState) {
+        Parcel parcel = Parcel.obtain();
+        rulesState.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+
+        RulesState newVersion = RulesState.CREATOR.createFromParcel(parcel);
+
+        assertEquals(rulesState, newVersion);
+    }
+
+    @Test
+    public void isSystemVersionOlderThan() {
+        RulesState rulesState = new RulesState(
+                "2016b", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
+        assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016a", 1)));
+        assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016b", 1)));
+        assertTrue(rulesState.isSystemVersionOlderThan(rulesVersion("2016c", 1)));
+    }
+
+    @Test
+    public void isInstalledDistroOlderThan() {
+        RulesState operationInProgress = new RulesState(
+                "2016b", formatVersion(1, 1), true /* operationInProgress */,
+                RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+                RulesState.STAGED_OPERATION_UNKNOWN, null /* installedDistroRulesVersion */);
+        try {
+            operationInProgress.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+
+        RulesState nothingInstalled = new RulesState(
+                "2016b", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+        try {
+            nothingInstalled.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+
+        DistroRulesVersion installedVersion = rulesVersion("2016b", 3);
+        RulesState rulesStateWithInstalledVersion = new RulesState(
+                "2016b", formatVersion(1, 1), false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_INSTALLED, installedVersion);
+
+        DistroRulesVersion olderRules = rulesVersion("2016a", 1);
+        assertEquals(installedVersion.isOlderThan(olderRules),
+                rulesStateWithInstalledVersion.isInstalledDistroOlderThan(olderRules));
+
+        DistroRulesVersion sameRules = rulesVersion("2016b", 1);
+        assertEquals(installedVersion.isOlderThan(sameRules),
+                rulesStateWithInstalledVersion.isInstalledDistroOlderThan(sameRules));
+
+        DistroRulesVersion newerRules = rulesVersion("2016c", 1);
+        assertEquals(installedVersion.isOlderThan(newerRules),
+                rulesStateWithInstalledVersion.isInstalledDistroOlderThan(newerRules));
+    }
+
+    private static void assertEqualsContract(RulesState one, RulesState two) {
+        assertEquals(one, two);
+        assertEquals(one.hashCode(), two.hashCode());
+    }
+
+    private static DistroRulesVersion rulesVersion(String rulesVersion, int revision) {
+        return new DistroRulesVersion(rulesVersion, revision);
+    }
+
+    private static DistroFormatVersion formatVersion(int majorVersion, int minorVersion) {
+        return new DistroFormatVersion(majorVersion, minorVersion);
+    }
+}
diff --git a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
new file mode 100644
index 0000000..e7a839c
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.app.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+import android.content.Context;
+import android.content.Intent;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+/**
+ * Tests for {@link RulesUpdaterContract}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class RulesUpdaterContractTest {
+
+    @Test
+    public void createUpdaterIntent() throws Exception {
+        String packageName = "foobar";
+        Intent intent = RulesUpdaterContract.createUpdaterIntent(packageName);
+
+        assertEquals(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK, intent.getAction());
+        assertEquals(packageName, intent.getPackage());
+        assertEquals(Intent.FLAG_INCLUDE_STOPPED_PACKAGES, intent.getFlags());
+    }
+
+    @Test
+    public void sendBroadcast() throws Exception {
+        String packageName = "foobar";
+        byte[] tokenBytes = new byte[] { 1, 2, 3, 4, 5 };
+
+        Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(packageName);
+        expectedIntent.putExtra(RulesUpdaterContract.EXTRA_CHECK_TOKEN, tokenBytes);
+
+        Context mockContext = mock(Context.class);
+
+        RulesUpdaterContract.sendBroadcast(mockContext, packageName, tokenBytes);
+
+        verify(mockContext).sendBroadcast(
+                filterEquals(expectedIntent),
+                eq(RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION));
+    }
+
+    /**
+     * Registers a mockito parameter matcher that uses {@link Intent#filterEquals(Intent)}. to
+     * check the parameter against the intent supplied.
+     */
+    private static Intent filterEquals(final Intent expected) {
+        final Matcher<Intent> m = new BaseMatcher<Intent>() {
+            @Override
+            public boolean matches(Object actual) {
+                return actual != null && expected.filterEquals((Intent) actual);
+            }
+            @Override
+            public void describeTo(Description description) {
+                description.appendText(expected.toString());
+            }
+        };
+        return argThat(m);
+    }
+}
diff --git a/core/tests/coretests/src/android/os/VintfObjectTest.java b/core/tests/coretests/src/android/os/VintfObjectTest.java
new file mode 100644
index 0000000..821ee80
--- /dev/null
+++ b/core/tests/coretests/src/android/os/VintfObjectTest.java
@@ -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.os;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+public class VintfObjectTest extends TestCase {
+    public void testReport() {
+        String[] xmls = VintfObject.report();
+        assertTrue(xmls.length > 0);
+        // From /system/manifest.xml
+        assertTrue(String.join("", xmls).contains(
+                "<manifest version=\"1.0\" type=\"framework\">"));
+        // From /system/compatibility-matrix.xml
+        assertTrue(String.join("", xmls).contains(
+                "<compatibility-matrix version=\"1.0\" type=\"framework\">"));
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
deleted file mode 100644
index 7f13abc..0000000
--- a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.net;
-
-import static android.net.NetworkStats.METERED_NO;
-import static android.net.NetworkStats.ROAMING_NO;
-import static android.net.NetworkStats.SET_ALL;
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.SET_FOREGROUND;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStats.UID_ALL;
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
-
-import android.content.res.Resources;
-import android.net.NetworkStats;
-import android.net.TrafficStats;
-import android.test.AndroidTestCase;
-
-import com.android.frameworks.coretests.R;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-/**
- * Tests for {@link NetworkStatsFactory}.
- */
-public class NetworkStatsFactoryTest extends AndroidTestCase {
-    private File mTestProc;
-    private NetworkStatsFactory mFactory;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mTestProc = new File(getContext().getFilesDir(), "proc");
-        if (mTestProc.exists()) {
-            IoUtils.deleteContents(mTestProc);
-        }
-
-        mFactory = new NetworkStatsFactory(mTestProc);
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        mFactory = null;
-
-        if (mTestProc.exists()) {
-            IoUtils.deleteContents(mTestProc);
-        }
-
-        super.tearDown();
-    }
-
-    public void testNetworkStatsDetail() throws Exception {
-        stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
-
-        final NetworkStats stats = mFactory.readNetworkStatsDetail();
-        assertEquals(70, stats.size());
-        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
-        assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
-        assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
-        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
-        assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
-    }
-
-    public void testKernelTags() throws Exception {
-        assertEquals(0, kernelToTag("0x0000000000000000"));
-        assertEquals(0x32, kernelToTag("0x0000003200000000"));
-        assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
-        assertEquals(0, kernelToTag("0x0000000000000000"));
-        assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
-
-        assertEquals(0, kernelToTag("0x0"));
-        assertEquals(0, kernelToTag("0xf00d"));
-        assertEquals(1, kernelToTag("0x100000000"));
-        assertEquals(14438007, kernelToTag("0xdc4e7700000000"));
-        assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000"));
-    }
-
-    public void testNetworkStatsWithSet() throws Exception {
-        stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
-
-        final NetworkStats stats = mFactory.readNetworkStatsDetail();
-        assertEquals(70, stats.size());
-        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L,
-                676L);
-        assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
-    }
-
-    public void testNetworkStatsSingle() throws Exception {
-        stageFile(R.raw.xt_qtaguid_iface_typical,
-                new File(mTestProc, "net/xt_qtaguid/iface_stat_all"));
-
-        final NetworkStats stats = mFactory.readNetworkStatsSummaryDev();
-        assertEquals(6, stats.size());
-        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L);
-        assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L);
-        assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
-    }
-
-    public void testNetworkStatsXt() throws Exception {
-        stageFile(R.raw.xt_qtaguid_iface_fmt_typical,
-                new File(mTestProc, "net/xt_qtaguid/iface_stat_fmt"));
-
-        final NetworkStats stats = mFactory.readNetworkStatsSummaryXt();
-        assertEquals(3, stats.size());
-        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L);
-        assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L,
-                2468L);
-        assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
-    }
-
-    /**
-     * Copy a {@link Resources#openRawResource(int)} into {@link File} for
-     * testing purposes.
-     */
-    private void stageFile(int rawId, File file) throws Exception {
-        new File(file.getParent()).mkdirs();
-        InputStream in = null;
-        OutputStream out = null;
-        try {
-            in = getContext().getResources().openRawResource(rawId);
-            out = new FileOutputStream(file);
-            Streams.copy(in, out);
-        } finally {
-            IoUtils.closeQuietly(in);
-            IoUtils.closeQuietly(out);
-        }
-    }
-
-    private void stageLong(long value, File file) throws Exception {
-        new File(file.getParent()).mkdirs();
-        FileWriter out = null;
-        try {
-            out = new FileWriter(file);
-            out.write(Long.toString(value));
-        } finally {
-            IoUtils.closeQuietly(out);
-        }
-    }
-
-    private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
-            int tag, long rxBytes, long txBytes) {
-        final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO);
-        final NetworkStats.Entry entry = stats.getValues(i, null);
-        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
-        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
-    }
-
-    private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
-            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
-        final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO);
-        final NetworkStats.Entry entry = stats.getValues(i, null);
-        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
-        assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
-        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
-        assertEquals("unexpected txPackets", txPackets, entry.txPackets);
-    }
-
-}
diff --git a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk
index c70c1d3..5a545e5 100644
--- a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk
@@ -22,6 +22,8 @@
 
 LOCAL_SDK_VERSION := current
 
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
 LOCAL_PACKAGE_NAME := ExternalLocAllPermsTestApp
 
 include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
index 7946e1a..b35cbae 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
@@ -20,6 +20,8 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
 LOCAL_SDK_VERSION := current
 
 LOCAL_PACKAGE_NAME := ExternalSharedPermsTestApp
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
index 657d0a4..06812b5 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
@@ -20,6 +20,8 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
 LOCAL_SDK_VERSION := current
 
 LOCAL_PACKAGE_NAME := ExternalSharedPermsBTTestApp
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
index d4450dc..48bceaf 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
@@ -20,6 +20,8 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
 LOCAL_SDK_VERSION := current
 
 LOCAL_PACKAGE_NAME := ExternalSharedPermsDiffKeyTestApp
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
index 8154862..569d102 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
@@ -20,6 +20,8 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
 LOCAL_SDK_VERSION := current
 
 LOCAL_PACKAGE_NAME := ExternalSharedPermsFLTestApp
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
index faf04b1..e69a2cc 100644
--- a/core/tests/utiltests/Android.mk
+++ b/core/tests/utiltests/Android.mk
@@ -17,7 +17,8 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     frameworks-base-testutils \
-    mockito-target
+    mockito-target \
+    legacy-android-tests
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
@@ -27,4 +28,4 @@
 
 include $(BUILD_PACKAGE)
 
-include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index e46f166..ae0c8a0 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -45,6 +45,7 @@
     <permission name="android.permission.BLUETOOTH_STACK" >
         <group gid="bluetooth" />
         <group gid="wakelock" />
+        <group gid="uhid" />
     </permission>
 
     <permission name="android.permission.NET_TUNNELING" >
diff --git a/keystore/tests/Android.mk b/keystore/tests/Android.mk
deleted file mode 100644
index a740b13..0000000
--- a/keystore/tests/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# We only want this apk build for tests.
-LOCAL_MODULE_TAGS := tests
-LOCAL_CERTIFICATE := platform
-
-LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := KeyStoreTests
-
-include $(BUILD_PACKAGE)
diff --git a/keystore/tests/AndroidManifest.xml b/keystore/tests/AndroidManifest.xml
deleted file mode 100644
index 415442f..0000000
--- a/keystore/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.security.tests"
-          android:sharedUserId="android.uid.system">
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
-        android:targetPackage="android.security.tests"
-        android:label="KeyStore Tests">
-    </instrumentation>
-</manifest>
diff --git a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java b/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
deleted file mode 100644
index bc8dd13..0000000
--- a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2012 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.security;
-
-import android.test.AndroidTestCase;
-
-import java.math.BigInteger;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-public class KeyPairGeneratorSpecTest extends AndroidTestCase {
-    private static final String TEST_ALIAS_1 = "test1";
-
-    private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
-
-    private static final long NOW_MILLIS = System.currentTimeMillis();
-
-    private static final BigInteger SERIAL_1 = BigInteger.ONE;
-
-    /* We have to round this off because X509v3 doesn't store milliseconds. */
-    private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
-
-    @SuppressWarnings("deprecation")
-    private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
-
-    public void testConstructor_Success() throws Exception {
-        KeyPairGeneratorSpec spec =
-                new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1,
-                        SERIAL_1, NOW, NOW_PLUS_10_YEARS, 0);
-
-        assertEquals("Context should be the one specified", getContext(), spec.getContext());
-
-        assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
-
-        assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType());
-
-        assertEquals("Key size should be the one specified", 1024, spec.getKeySize());
-
-        assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
-
-        assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
-
-        assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate());
-    }
-
-    public void testBuilder_Success() throws Exception {
-        KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext())
-                .setAlias(TEST_ALIAS_1)
-                .setKeyType("RSA")
-                .setKeySize(1024)
-                .setSubject(TEST_DN_1)
-                .setSerialNumber(SERIAL_1)
-                .setStartDate(NOW)
-                .setEndDate(NOW_PLUS_10_YEARS)
-                .setEncryptionRequired()
-                .build();
-
-        assertEquals("Context should be the one specified", getContext(), spec.getContext());
-
-        assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
-
-        assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType());
-
-        assertEquals("Key size should be the one specified", 1024, spec.getKeySize());
-
-        assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
-
-        assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
-
-        assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate());
-
-        assertEquals("encryption flag should be on", KeyStore.FLAG_ENCRYPTED, spec.getFlags());
-    }
-
-    public void testConstructor_NullContext_Failure() throws Exception {
-        try {
-            new KeyPairGeneratorSpec(null, TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW,
-                    NOW_PLUS_10_YEARS, 0);
-            fail("Should throw IllegalArgumentException when context is null");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testConstructor_NullKeystoreAlias_Failure() throws Exception {
-        try {
-            new KeyPairGeneratorSpec(getContext(), null, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW,
-                    NOW_PLUS_10_YEARS, 0);
-            fail("Should throw IllegalArgumentException when keystoreAlias is null");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testConstructor_NullSubjectDN_Failure() throws Exception {
-        try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, null, SERIAL_1, NOW,
-                    NOW_PLUS_10_YEARS, 0);
-            fail("Should throw IllegalArgumentException when subjectDN is null");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testConstructor_NullSerial_Failure() throws Exception {
-        try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, null, NOW,
-                    NOW_PLUS_10_YEARS, 0);
-            fail("Should throw IllegalArgumentException when startDate is null");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testConstructor_NullStartDate_Failure() throws Exception {
-        try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
-                    null, NOW_PLUS_10_YEARS, 0);
-            fail("Should throw IllegalArgumentException when startDate is null");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testConstructor_NullEndDate_Failure() throws Exception {
-        try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
-                    NOW, null, 0);
-            fail("Should throw IllegalArgumentException when keystoreAlias is null");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testConstructor_EndBeforeStart_Failure() throws Exception {
-        try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
-                    NOW_PLUS_10_YEARS, NOW, 0);
-            fail("Should throw IllegalArgumentException when end is before start");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-}
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
deleted file mode 100644
index 319cf32..0000000
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ /dev/null
@@ -1,974 +0,0 @@
-/*
- * Copyright (C) 2009 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.security;
-
-import android.app.Activity;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Process;
-import android.security.keymaster.ExportResult;
-import android.security.keymaster.KeyCharacteristics;
-import android.security.keymaster.KeymasterArguments;
-import android.security.keymaster.KeymasterBlob;
-import android.security.keymaster.KeymasterDefs;
-import android.security.keymaster.OperationResult;
-import android.test.ActivityUnitTestCase;
-import android.test.AssertionFailedError;
-import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.MediumTest;
-import com.android.org.conscrypt.NativeConstants;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.security.spec.RSAKeyGenParameterSpec;
-
-/**
- * Junit / Instrumentation test case for KeyStore class
- *
- * Running the test suite:
- *
- *  runtest keystore-unit
- *
- * Or this individual test case:
- *
- *  runtest --path frameworks/base/keystore/tests/src/android/security/KeyStoreTest.java
- */
-@MediumTest
-public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
-    private static final String TEST_PASSWD = "12345678";
-    private static final String TEST_PASSWD2 = "87654321";
-    private static final String TEST_KEYNAME = "test-key";
-    private static final String TEST_KEYNAME1 = "test-key.1";
-    private static final String TEST_KEYNAME2 = "test-key\02";
-    private static final byte[] TEST_KEYVALUE = "test value".getBytes(StandardCharsets.UTF_8);
-
-    // "Hello, World" in Chinese
-    private static final String TEST_I18N_KEY = "\u4F60\u597D, \u4E16\u754C";
-    private static final byte[] TEST_I18N_VALUE = TEST_I18N_KEY.getBytes(StandardCharsets.UTF_8);
-
-    // Test vector data for signatures
-    private static final int RSA_KEY_SIZE = 1024;
-    private static final byte[] TEST_DATA =  new byte[RSA_KEY_SIZE / 8];
-    static {
-        for (int i = 0; i < TEST_DATA.length; i++) {
-            TEST_DATA[i] = (byte) i;
-        }
-    }
-
-    private KeyStore mKeyStore = null;
-
-    public KeyStoreTest() {
-        super(Activity.class);
-    }
-
-    private static final byte[] PRIVKEY_BYTES = hexToBytes(
-            "308204BE020100300D06092A864886F70D0101010500048204A8308204A4020100028201" +
-            "0100E0473E8AB8F2284FEB9E742FF9748FA118ED98633C92F52AEB7A2EBE0D3BE60329BE" +
-            "766AD10EB6A515D0D2CFD9BEA7930F0C306537899F7958CD3E85B01F8818524D312584A9" +
-            "4B251E3625B54141EDBFEE198808E1BB97FC7CB49B9EAAAF68E9C98D7D0EDC53BBC0FA00" +
-            "34356D6305FBBCC3C7001405386ABBC873CB0F3EF7425F3D33DF7B315AE036D2A0B66AFD" +
-            "47503B169BF36E3B5162515B715FDA83DEAF2C58AEB9ABFB3097C3CC9DD9DBE5EF296C17" +
-            "6139028E8A671E63056D45F40188D2C4133490845DE52C2534E9C6B2478C07BDAE928823" +
-            "B62D066C7770F9F63F3DBA247F530844747BE7AAA85D853B8BD244ACEC3DE3C89AB46453" +
-            "AB4D24C3AC6902030100010282010037784776A5F17698F5AC960DFB83A1B67564E648BD" +
-            "0597CF8AB8087186F2669C27A9ECBDD480F0197A80D07309E6C6A96F925331E57F8B4AC6" +
-            "F4D45EDA45A23269C09FC428C07A4E6EDF738A15DEC97FABD2F2BB47A14F20EA72FCFE4C" +
-            "36E01ADA77BD137CD8D4DA10BB162E94A4662971F175F985FA188F056CB97EE2816F43AB" +
-            "9D3747612486CDA8C16196C30818A995EC85D38467791267B3BF21F273710A6925862576" +
-            "841C5B6712C12D4BD20A2F3299ADB7C135DA5E9515ABDA76E7CAF2A3BE80551D073B78BF" +
-            "1162C48AD2B7F4743A0238EE4D252F7D5E7E6533CCAE64CCB39360075A2FD1E034EC3AE5" +
-            "CE9C408CCBF0E25E4114021687B3DD4754AE8102818100F541884BC3737B2922D4119EF4" +
-            "5E2DEE2CD4CBB75F45505A157AA5009F99C73A2DF0724AC46024306332EA898177634546" +
-            "5DC6DF1E0A6F140AFF3B7396E6A8994AC5DAA96873472FE37749D14EB3E075E629DBEB35" +
-            "83338A6F3649D0A2654A7A42FD9AB6BFA4AC4D481D390BB229B064BDC311CC1BE1B63189" +
-            "DA7C40CDECF2B102818100EA1A742DDB881CEDB7288C87E38D868DD7A409D15A43F445D5" +
-            "377A0B5731DDBFCA2DAF28A8E13CD5C0AFCEC3347D74A39E235A3CD9633F274DE2B94F92" +
-            "DF43833911D9E9F1CF58F27DE2E08FF45964C720D3EC2139DC7CAFC912953CDECB2F355A" +
-            "2E2C35A50FAD754CB3B23166424BA3B6E3112A2B898C38C5C15EDB238693390281805182" +
-            "8F1EC6FD996029901BAF1D7E337BA5F0AF27E984EAD895ACE62BD7DF4EE45A224089F2CC" +
-            "151AF3CD173FCE0474BCB04F386A2CDCC0E0036BA2419F54579262D47100BE931984A3EF" +
-            "A05BECF141574DC079B3A95C4A83E6C43F3214D6DF32D512DE198085E531E616B83FD7DD" +
-            "9D1F4E2607C3333D07C55D107D1D3893587102818100DB4FB50F50DE8EDB53FF34C80931" +
-            "88A0512867DA2CCA04897759E587C244010DAF8664D59E8083D16C164789301F67A9F078" +
-            "060D834A2ADBD367575B68A8A842C2B02A89B3F31FCCEC8A22FE395795C5C6C7422B4E5D" +
-            "74A1E9A8F30E7759B9FC2D639C1F15673E84E93A5EF1506F4315383C38D45CBD1B14048F" +
-            "4721DC82326102818100D8114593AF415FB612DBF1923710D54D07486205A76A3B431949" +
-            "68C0DFF1F11EF0F61A4A337D5FD3741BBC9640E447B8B6B6C47C3AC1204357D3B0C55BA9" +
-            "286BDA73F629296F5FA9146D8976357D3C751E75148696A40B74685C82CE30902D639D72" +
-            "4FF24D5E2E9407EE34EDED2E3B4DF65AA9BCFEB6DF28D07BA6903F165768");
-
-    private static final byte[] AES256_BYTES = hexToBytes(
-            "0CC175B9C0F1B6A831C399E269772661CEC520EA51EA0A47E87295FA3245A605");
-
-    private static byte[] hexToBytes(String s) {
-        int len = s.length();
-        byte[] data = new byte[len / 2];
-        for (int i = 0; i < len; i += 2) {
-            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
-                    s.charAt(i + 1), 16));
-        }
-        return data;
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        mKeyStore = KeyStore.getInstance();
-        if (mKeyStore.state() != KeyStore.State.UNINITIALIZED) {
-            mKeyStore.reset();
-        }
-        assertEquals("KeyStore should be in an uninitialized state",
-                KeyStore.State.UNINITIALIZED, mKeyStore.state());
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mKeyStore.reset();
-        super.tearDown();
-    }
-
-    public void testState() throws Exception {
-        assertEquals(KeyStore.State.UNINITIALIZED, mKeyStore.state());
-    }
-
-    public void testPassword() throws Exception {
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-        assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
-    }
-
-    public void testGet() throws Exception {
-        assertNull(mKeyStore.get(TEST_KEYNAME));
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertNull(mKeyStore.get(TEST_KEYNAME));
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
-                KeyStore.FLAG_ENCRYPTED));
-        assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
-    }
-
-    public void testPut() throws Exception {
-        assertNull(mKeyStore.get(TEST_KEYNAME));
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
-                KeyStore.FLAG_ENCRYPTED));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
-                KeyStore.FLAG_ENCRYPTED));
-        assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
-    }
-
-    public void testPut_grantedUid_Wifi() throws Exception {
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
-                KeyStore.FLAG_ENCRYPTED));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
-                KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-    }
-
-    public void testPut_ungrantedUid_Bluetooth() throws Exception {
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
-                KeyStore.FLAG_ENCRYPTED));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
-                KeyStore.FLAG_ENCRYPTED));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-    }
-
-    public void testI18n() throws Exception {
-        assertFalse(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
-                KeyStore.FLAG_ENCRYPTED));
-        assertFalse(mKeyStore.contains(TEST_I18N_KEY));
-        mKeyStore.onUserPasswordChanged(TEST_I18N_KEY);
-        assertTrue(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
-                KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mKeyStore.contains(TEST_I18N_KEY));
-    }
-
-    public void testDelete() throws Exception {
-        assertFalse(mKeyStore.delete(TEST_KEYNAME));
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertFalse(mKeyStore.delete(TEST_KEYNAME));
-
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
-                KeyStore.FLAG_ENCRYPTED));
-        assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
-        assertTrue(mKeyStore.delete(TEST_KEYNAME));
-        assertNull(mKeyStore.get(TEST_KEYNAME));
-    }
-
-    public void testDelete_grantedUid_Wifi() throws Exception {
-        assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
-
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
-                KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        assertTrue(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-    }
-
-    public void testDelete_ungrantedUid_Bluetooth() throws Exception {
-        assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
-                KeyStore.FLAG_ENCRYPTED));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-        assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-    }
-
-    public void testContains() throws Exception {
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
-                KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-    }
-
-    public void testContains_grantedUid_Wifi() throws Exception {
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
-                KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-    }
-
-    public void testContains_grantedUid_Bluetooth() throws Exception {
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
-        assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
-                KeyStore.FLAG_ENCRYPTED));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-    }
-
-    public void testList() throws Exception {
-        String[] emptyResult = mKeyStore.list(TEST_KEYNAME);
-        assertNotNull(emptyResult);
-        assertEquals(0, emptyResult.length);
-
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
-        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
-
-        String[] results = mKeyStore.list(TEST_KEYNAME);
-        assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
-                                               TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
-                     new HashSet(Arrays.asList(results)));
-    }
-
-    public void testList_ungrantedUid_Bluetooth() throws Exception {
-        String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID);
-        assertEquals(0, results1.length);
-
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
-        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
-
-        String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID);
-        assertEquals(0, results2.length);
-    }
-
-    public void testList_grantedUid_Wifi() throws Exception {
-        String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID);
-        assertNotNull(results1);
-        assertEquals(0, results1.length);
-
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
-        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
-
-        String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID);
-        assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
-                                               TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
-                     new HashSet(Arrays.asList(results2)));
-    }
-
-    public void testList_grantedUid_Vpn() throws Exception {
-        String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID);
-        assertNotNull(results1);
-        assertEquals(0, results1.length);
-
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
-        mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
-
-        String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID);
-        assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
-                                               TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
-                     new HashSet(Arrays.asList(results2)));
-    }
-
-    public void testLock() throws Exception {
-        assertFalse(mKeyStore.lock());
-
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
-
-        assertTrue(mKeyStore.lock());
-        assertEquals(KeyStore.State.LOCKED, mKeyStore.state());
-    }
-
-    public void testUnlock() throws Exception {
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
-        mKeyStore.lock();
-
-        assertFalse(mKeyStore.unlock(TEST_PASSWD2));
-        assertTrue(mKeyStore.unlock(TEST_PASSWD));
-    }
-
-    public void testIsEmpty() throws Exception {
-        assertTrue(mKeyStore.isEmpty());
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        assertTrue(mKeyStore.isEmpty());
-        mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
-        assertFalse(mKeyStore.isEmpty());
-        mKeyStore.reset();
-        assertTrue(mKeyStore.isEmpty());
-    }
-
-    public void testGenerate_NotInitialized_Fail() throws Exception {
-        assertFalse("Should fail when keystore is not initialized",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                        RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-    }
-
-    public void testGenerate_Locked_Fail() throws Exception {
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        mKeyStore.lock();
-        assertFalse("Should fail when keystore is locked",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                        RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-    }
-
-    public void testGenerate_Success() throws Exception {
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to generate key when unlocked",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                        RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-    }
-
-    public void testGenerate_grantedUid_Wifi_Success() throws Exception {
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to generate key when unlocked",
-                mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID, NativeConstants.EVP_PKEY_RSA,
-                        RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-    }
-
-    public void testGenerate_ungrantedUid_Bluetooth_Failure() throws Exception {
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID,
-                    NativeConstants.EVP_PKEY_RSA, RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-    }
-
-    public void testImport_Success() throws Exception {
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
-                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-    }
-
-    public void testImport_grantedUid_Wifi_Success() throws Exception {
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
-                PRIVKEY_BYTES, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-    }
-
-    public void testImport_ungrantedUid_Bluetooth_Failure() throws Exception {
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertFalse(mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES, Process.BLUETOOTH_UID,
-                KeyStore.FLAG_ENCRYPTED));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-    }
-
-    public void testImport_Failure_BadEncoding() throws Exception {
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
-        assertFalse("Invalid DER-encoded key should not be imported", mKeyStore.importKey(
-                TEST_KEYNAME, TEST_DATA, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-    }
-
-    public void testSign_Success() throws Exception {
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
-        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                    RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
-
-        assertNotNull("Signature should not be null", signature);
-    }
-
-    public void testVerify_Success() throws Exception {
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
-        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                    RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
-
-        assertNotNull("Signature should not be null", signature);
-
-        assertTrue("Signature should verify with same data",
-                mKeyStore.verify(TEST_KEYNAME, TEST_DATA, signature));
-    }
-
-    public void testSign_NotInitialized_Failure() throws Exception {
-        assertNull("Should not be able to sign without first initializing the keystore",
-                mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
-    }
-
-    public void testSign_NotGenerated_Failure() throws Exception {
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
-        assertNull("Should not be able to sign without first generating keys",
-                mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
-    }
-
-    public void testGrant_Generated_Success() throws Exception {
-        assertTrue("Password should work for keystore",
-                mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                        RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
-        assertTrue("Should be able to grant key to other user",
-                mKeyStore.grant(TEST_KEYNAME, 0));
-    }
-
-    public void testGrant_Imported_Success() throws Exception {
-        assertTrue("Password should work for keystore", mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
-                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0));
-    }
-
-    public void testGrant_NoKey_Failure() throws Exception {
-        assertTrue("Should be able to unlock keystore for test",
-                mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertFalse("Should not be able to grant without first initializing the keystore",
-                mKeyStore.grant(TEST_KEYNAME, 0));
-    }
-
-    public void testGrant_NotInitialized_Failure() throws Exception {
-        assertFalse("Should not be able to grant without first initializing the keystore",
-                mKeyStore.grant(TEST_KEYNAME, 0));
-    }
-
-    public void testUngrant_Generated_Success() throws Exception {
-        assertTrue("Password should work for keystore",
-                mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                        RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
-        assertTrue("Should be able to grant key to other user",
-                mKeyStore.grant(TEST_KEYNAME, 0));
-
-        assertTrue("Should be able to ungrant key to other user",
-                mKeyStore.ungrant(TEST_KEYNAME, 0));
-    }
-
-    public void testUngrant_Imported_Success() throws Exception {
-        assertTrue("Password should work for keystore",
-                mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
-                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertTrue("Should be able to grant key to other user",
-                mKeyStore.grant(TEST_KEYNAME, 0));
-
-        assertTrue("Should be able to ungrant key to other user",
-                mKeyStore.ungrant(TEST_KEYNAME, 0));
-    }
-
-    public void testUngrant_NotInitialized_Failure() throws Exception {
-        assertFalse("Should fail to ungrant key when keystore not initialized",
-                mKeyStore.ungrant(TEST_KEYNAME, 0));
-    }
-
-    public void testUngrant_NoGrant_Failure() throws Exception {
-        assertTrue("Password should work for keystore",
-                mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                        RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
-        assertFalse("Should not be able to revoke not existent grant",
-                mKeyStore.ungrant(TEST_KEYNAME, 0));
-    }
-
-    public void testUngrant_DoubleUngrant_Failure() throws Exception {
-        assertTrue("Password should work for keystore",
-                mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                        RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
-        assertTrue("Should be able to grant key to other user",
-                mKeyStore.grant(TEST_KEYNAME, 0));
-
-        assertTrue("Should be able to ungrant key to other user",
-                mKeyStore.ungrant(TEST_KEYNAME, 0));
-
-        assertFalse("Should fail to ungrant key to other user second time",
-                mKeyStore.ungrant(TEST_KEYNAME, 0));
-    }
-
-    public void testUngrant_DoubleGrantUngrant_Failure() throws Exception {
-        assertTrue("Password should work for keystore",
-                mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                        RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
-        assertTrue("Should be able to grant key to other user",
-                mKeyStore.grant(TEST_KEYNAME, 0));
-
-        assertTrue("Should be able to grant key to other user a second time",
-                mKeyStore.grant(TEST_KEYNAME, 0));
-
-        assertTrue("Should be able to ungrant key to other user",
-                mKeyStore.ungrant(TEST_KEYNAME, 0));
-
-        assertFalse("Should fail to ungrant key to other user second time",
-                mKeyStore.ungrant(TEST_KEYNAME, 0));
-    }
-
-    public void testDuplicate_grantedUid_Wifi_Success() throws Exception {
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
-        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                    RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-
-        // source doesn't exist
-        assertFalse(mKeyStore.duplicate(TEST_KEYNAME1, -1, TEST_KEYNAME1, Process.WIFI_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID));
-
-        // Copy from current UID to granted UID
-        assertTrue(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME1, Process.WIFI_UID));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME1));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID));
-        assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME1, Process.WIFI_UID));
-
-        // Copy from granted UID to same granted UID
-        assertTrue(mKeyStore.duplicate(TEST_KEYNAME1, Process.WIFI_UID, TEST_KEYNAME2,
-                Process.WIFI_UID));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME2, Process.WIFI_UID));
-        assertFalse(mKeyStore.duplicate(TEST_KEYNAME1, Process.WIFI_UID, TEST_KEYNAME2,
-                Process.WIFI_UID));
-
-        assertTrue(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, -1));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME1));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME2));
-        assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, -1));
-    }
-
-    public void testDuplicate_ungrantedUid_Bluetooth_Failure() throws Exception {
-        assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
-        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
-                    RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
-        assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, Process.BLUETOOTH_UID));
-        assertFalse(mKeyStore.duplicate(TEST_KEYNAME, Process.BLUETOOTH_UID, TEST_KEYNAME2,
-                Process.BLUETOOTH_UID));
-
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-    }
-
-    /**
-     * The amount of time to allow before and after expected time for variance
-     * in timing tests.
-     */
-    private static final long SLOP_TIME_MILLIS = 15000L;
-
-    public void testGetmtime_Success() throws Exception {
-        assertTrue("Password should work for keystore",
-                mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
-                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        long now = System.currentTimeMillis();
-        long actual = mKeyStore.getmtime(TEST_KEYNAME);
-
-        long expectedAfter = now - SLOP_TIME_MILLIS;
-        long expectedBefore = now + SLOP_TIME_MILLIS;
-
-        assertLessThan("Time should be close to current time", expectedBefore, actual);
-        assertGreaterThan("Time should be close to current time", expectedAfter, actual);
-    }
-
-    private static void assertLessThan(String explanation, long expectedBefore, long actual) {
-        if (actual >= expectedBefore) {
-            throw new AssertionFailedError(explanation + ": actual=" + actual
-                    + ", expected before: " + expectedBefore);
-        }
-    }
-
-    private static void assertGreaterThan(String explanation, long expectedAfter, long actual) {
-        if (actual <= expectedAfter) {
-            throw new AssertionFailedError(explanation + ": actual=" + actual
-                    + ", expected after: " + expectedAfter);
-        }
-    }
-
-    public void testGetmtime_NonExist_Failure() throws Exception {
-        assertTrue("Password should work for keystore",
-                mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
-        assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
-                PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertEquals("-1 should be returned for non-existent key",
-                -1L, mKeyStore.getmtime(TEST_KEYNAME2));
-    }
-
-    private KeyCharacteristics generateRsaKey(String name) throws Exception {
-        KeymasterArguments args = new KeymasterArguments();
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
-        args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
-        args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-        args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
-        args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
-
-        KeyCharacteristics outCharacteristics = new KeyCharacteristics();
-        int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
-        assertEquals("generateRsaKey should succeed", KeyStore.NO_ERROR, result);
-        return outCharacteristics;
-    }
-
-    public void testGenerateKey() throws Exception {
-        generateRsaKey("test");
-        mKeyStore.delete("test");
-    }
-
-    public void testGenerateRsaWithEntropy() throws Exception {
-        byte[] entropy = new byte[] {1,2,3,4,5};
-        String name = "test";
-        KeymasterArguments args = new KeymasterArguments();
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
-        args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
-        args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-        args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
-        args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
-
-        KeyCharacteristics outCharacteristics = new KeyCharacteristics();
-        int result = mKeyStore.generateKey(name, args, entropy, 0, outCharacteristics);
-        assertEquals("generateKey should succeed", KeyStore.NO_ERROR, result);
-    }
-
-    public void testGenerateAndDelete() throws Exception {
-        generateRsaKey("test");
-        assertTrue("delete should succeed", mKeyStore.delete("test"));
-    }
-
-    public void testGetKeyCharacteristicsSuccess() throws Exception {
-        mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-        String name = "test";
-        KeyCharacteristics gen = generateRsaKey(name);
-        KeyCharacteristics call = new KeyCharacteristics();
-        int result = mKeyStore.getKeyCharacteristics(name, null, null, call);
-        assertEquals("getKeyCharacteristics should succeed", KeyStore.NO_ERROR, result);
-        mKeyStore.delete("test");
-    }
-
-    public void testAppId() throws Exception {
-        String name = "test";
-        byte[] id = new byte[] {0x01, 0x02, 0x03};
-        KeymasterArguments args = new KeymasterArguments();
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
-        args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
-        args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
-        args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
-        args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-        args.addBytes(KeymasterDefs.KM_TAG_APPLICATION_ID, id);
-        args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
-
-        KeyCharacteristics outCharacteristics = new KeyCharacteristics();
-        int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
-        assertEquals("generateRsaKey should succeed", KeyStore.NO_ERROR, result);
-        assertEquals("getKeyCharacteristics should fail without application ID",
-                KeymasterDefs.KM_ERROR_INVALID_KEY_BLOB,
-                mKeyStore.getKeyCharacteristics(name, null, null, outCharacteristics));
-        assertEquals("getKeyCharacteristics should succeed with application ID",
-                KeyStore.NO_ERROR,
-                mKeyStore.getKeyCharacteristics(name, new KeymasterBlob(id), null,
-                    outCharacteristics));
-    }
-
-
-    public void testExportRsa() throws Exception {
-        String name = "test";
-        generateRsaKey(name);
-        ExportResult result = mKeyStore.exportKey(name, KeymasterDefs.KM_KEY_FORMAT_X509, null,
-                null);
-        assertEquals("Export success", KeyStore.NO_ERROR, result.resultCode);
-        // TODO: Verify we have an RSA public key that's well formed.
-    }
-
-    public void testAesGcmEncryptSuccess() throws Exception {
-        String name = "test";
-        KeymasterArguments args = new KeymasterArguments();
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
-        args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
-        args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
-        args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
-        args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-
-        KeyCharacteristics outCharacteristics = new KeyCharacteristics();
-        int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
-        assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
-
-        args = new KeymasterArguments();
-        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
-        args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
-        args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
-        args.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 128);
-        OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
-                true, args, null);
-        IBinder token = result.token;
-        assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
-        result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04});
-        assertEquals("Update should succeed", KeyStore.NO_ERROR, result.resultCode);
-        assertEquals("Finish should succeed", KeyStore.NO_ERROR,
-                mKeyStore.finish(token, null, null).resultCode);
-        // TODO: Assert that an AEAD tag was returned by finish
-    }
-
-    public void testBadToken() throws Exception {
-        IBinder token = new Binder();
-        OperationResult result = mKeyStore.update(token, null, new byte[] {0x01});
-        assertEquals("Update with invalid token should fail",
-                KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE, result.resultCode);
-    }
-
-    private int importAesKey(String name, byte[] key, int size, int mode) {
-        KeymasterArguments args = new KeymasterArguments();
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
-        args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
-        args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mode);
-        args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, size);
-        args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-        return mKeyStore.importKey(name, args, KeymasterDefs.KM_KEY_FORMAT_RAW, key, 0,
-                new KeyCharacteristics());
-    }
-    private byte[] doOperation(String name, int purpose, byte[] in, KeymasterArguments beginArgs) {
-        OperationResult result = mKeyStore.begin(name, purpose,
-                true, beginArgs, null);
-        assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
-        IBinder token = result.token;
-        result = mKeyStore.update(token, null, in);
-        assertEquals("Update should succeed", KeyStore.NO_ERROR, result.resultCode);
-        assertEquals("All data should be consumed", in.length, result.inputConsumed);
-        assertEquals("Finish should succeed", KeyStore.NO_ERROR,
-                mKeyStore.finish(token, null, null).resultCode);
-        return result.output;
-    }
-
-    public void testImportAes() throws Exception {
-        int result = importAesKey("aes", AES256_BYTES, 256, KeymasterDefs.KM_MODE_ECB);
-        assertEquals("import should succeed", KeyStore.NO_ERROR, result);
-        mKeyStore.delete("aes");
-    }
-
-    public void testAes256Ecb() throws Exception {
-        byte[] key =
-                hexToBytes("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
-        String name = "aes";
-        assertEquals(KeyStore.NO_ERROR, importAesKey(name, key, 256, KeymasterDefs.KM_MODE_ECB));
-        byte[][] testVectors = new byte[][] {
-            hexToBytes("6bc1bee22e409f96e93d7e117393172a"),
-            hexToBytes("ae2d8a571e03ac9c9eb76fac45af8e51"),
-            hexToBytes("30c81c46a35ce411e5fbc1191a0a52ef"),
-            hexToBytes("f69f2445df4f9b17ad2b417be66c3710")};
-        byte[][] cipherVectors = new byte[][] {
-            hexToBytes("f3eed1bdb5d2a03c064b5a7e3db181f8"),
-            hexToBytes("591ccb10d410ed26dc5ba74a31362870"),
-            hexToBytes("b6ed21b99ca6f4f9f153e7b1beafed1d"),
-            hexToBytes("23304b7a39f9f3ff067d8d8f9e24ecc7")};
-        KeymasterArguments beginArgs = new KeymasterArguments();
-        beginArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
-        beginArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
-        beginArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
-        for (int i = 0; i < testVectors.length; i++) {
-            byte[] cipherText = doOperation(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, testVectors[i],
-                    beginArgs);
-            MoreAsserts.assertEquals(cipherVectors[i], cipherText);
-        }
-        for (int i = 0; i < testVectors.length; i++) {
-            byte[] plainText = doOperation(name, KeymasterDefs.KM_PURPOSE_DECRYPT,
-                    cipherVectors[i], beginArgs);
-            MoreAsserts.assertEquals(testVectors[i], plainText);
-        }
-    }
-
-    // This is a very implementation specific test and should be thrown out eventually, however it
-    // is nice for now to test that keystore is properly pruning operations.
-    public void testOperationPruning() throws Exception {
-        String name = "test";
-        KeymasterArguments args = new KeymasterArguments();
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
-        args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
-        args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
-        args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
-        args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-
-        KeyCharacteristics outCharacteristics = new KeyCharacteristics();
-        int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
-        assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
-
-        args = new KeymasterArguments();
-        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
-        args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
-        args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
-        OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
-                true, args, null);
-        assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
-        IBinder first = result.token;
-        // Implementation detail: softkeymaster supports 16 concurrent operations
-        for (int i = 0; i < 16; i++) {
-            result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, true, args, null);
-            assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
-        }
-        // At this point the first operation should be pruned.
-        assertEquals("Operation should be pruned", KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE,
-                mKeyStore.update(first, null, new byte[] {0x01}).resultCode);
-    }
-
-    public void testAuthNeeded() throws Exception {
-        String name = "test";
-        KeymasterArguments args = new KeymasterArguments();
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
-        args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
-        args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
-        args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
-        args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
-        args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1);
-
-        KeyCharacteristics outCharacteristics = new KeyCharacteristics();
-        int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
-        assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
-        OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
-                true, args, null);
-        assertEquals("Begin should expect authorization", KeyStore.OP_AUTH_NEEDED,
-                result.resultCode);
-        IBinder token = result.token;
-        result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04});
-        assertEquals("Update should require authorization",
-                KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED, result.resultCode);
-    }
-
-    public void testPasswordRemovalEncryptedEntry() throws Exception {
-        mKeyStore.onUserPasswordChanged("test");
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
-                KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
-        mKeyStore.onUserPasswordChanged("");
-        // Removing the password should have deleted all entries using FLAG_ENCRYPTED
-        assertNull(mKeyStore.get(TEST_KEYNAME));
-        assertFalse(mKeyStore.contains(TEST_KEYNAME));
-    }
-
-    public void testPasswordRemovalUnencryptedEntry() throws Exception {
-        mKeyStore.onUserPasswordChanged("test");
-        assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
-                KeyStore.FLAG_NONE));
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
-        mKeyStore.onUserPasswordChanged("");
-        // Removing the password should not delete unencrypted entries.
-        assertTrue(mKeyStore.contains(TEST_KEYNAME));
-        assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
-    }
-}
diff --git a/keystore/tests/src/android/security/SystemKeyStoreTest.java b/keystore/tests/src/android/security/SystemKeyStoreTest.java
deleted file mode 100644
index ecf7cbc..0000000
--- a/keystore/tests/src/android/security/SystemKeyStoreTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2010 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.security;
-
-import android.app.Activity;
-import android.security.SystemKeyStore;
-import android.test.ActivityUnitTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-/**
- * Junit / Instrumentation test case for KeyStore class
- *
- * Running the test suite:
- *
- *  runtest keystore-unit
- *
- * Or this individual test case:
- *
- *  runtest --path frameworks/base/keystore/tests/src/android/security/SystemKeyStoreTest.java
- */
-@MediumTest
-public class SystemKeyStoreTest extends ActivityUnitTestCase<Activity> {
-
-    private static final String keyName = "TestKey";
-    private static final String keyName2 = "TestKey2";
-    private SystemKeyStore mSysKeyStore = null;
-
-    public SystemKeyStoreTest() {
-        super(Activity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        mSysKeyStore = SystemKeyStore.getInstance();
-        try {
-            mSysKeyStore.deleteKey(keyName);
-            mSysKeyStore.deleteKey(keyName2);
-        } catch (Exception e) { }
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        try {
-            mSysKeyStore.deleteKey(keyName);
-            mSysKeyStore.deleteKey(keyName2);
-        } catch (Exception e) { }
-        super.tearDown();
-    }
-
-    public void testBasicAccess() throws Exception {
-        try {
-            byte[] newKey = mSysKeyStore.generateNewKey(128, "AES", keyName);
-            assertNotNull(newKey);
-            byte[] recKey = mSysKeyStore.retrieveKey(keyName);
-            assertEquals(newKey.length, recKey.length);
-            for (int i = 0; i < newKey.length; i++) {
-                assertEquals(newKey[i], recKey[i]);
-            }
-            mSysKeyStore.deleteKey(keyName);
-            byte[] nullKey = mSysKeyStore.retrieveKey(keyName);
-            assertNull(nullKey);
-
-            String newKeyStr = mSysKeyStore.generateNewKeyHexString(128, "AES", keyName2);
-            assertNotNull(newKeyStr);
-            String recKeyStr = mSysKeyStore.retrieveKeyHexString(keyName2);
-            assertEquals(newKeyStr, recKeyStr);
-
-            mSysKeyStore.deleteKey(keyName2);
-            String nullKey2 = mSysKeyStore.retrieveKeyHexString(keyName2);
-            assertNull(nullKey2);
-        } catch (Exception e) {
-            fail();
-        }
-    }
-}
diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
deleted file mode 100644
index 1af0b7d..0000000
--- a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2012 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.security.keystore;
-
-import android.security.Credentials;
-import android.security.KeyPairGeneratorSpec;
-import android.security.KeyStore;
-import android.security.keymaster.ExportResult;
-import android.security.keymaster.KeymasterDefs;
-import android.test.AndroidTestCase;
-
-import java.io.ByteArrayInputStream;
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.RSAKeyGenParameterSpec;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
-    private android.security.KeyStore mAndroidKeyStore;
-
-    private java.security.KeyPairGenerator mGenerator;
-
-    private static final String TEST_ALIAS_1 = "test1";
-
-    private static final String TEST_ALIAS_2 = "test2";
-
-    private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
-
-    private static final X500Principal TEST_DN_2 = new X500Principal("CN=test2");
-
-    private static final BigInteger TEST_SERIAL_1 = BigInteger.ONE;
-
-    private static final BigInteger TEST_SERIAL_2 = BigInteger.valueOf(2L);
-
-    private static final long NOW_MILLIS = System.currentTimeMillis();
-
-    /* We have to round this off because X509v3 doesn't store milliseconds. */
-    private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
-
-    @SuppressWarnings("deprecation")
-    private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
-
-    @Override
-    protected void setUp() throws Exception {
-        mAndroidKeyStore = android.security.KeyStore.getInstance();
-
-        assertTrue(mAndroidKeyStore.reset());
-
-        assertFalse(mAndroidKeyStore.isUnlocked());
-
-        mGenerator = java.security.KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
-    }
-
-    private void setupPassword() {
-        assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
-        assertTrue(mAndroidKeyStore.isUnlocked());
-
-        String[] aliases = mAndroidKeyStore.list("");
-        assertNotNull(aliases);
-        assertEquals(0, aliases.length);
-    }
-
-    public void testKeyPairGenerator_Initialize_Params_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                .setAlias(TEST_ALIAS_1)
-                .setSubject(TEST_DN_1)
-                .setSerialNumber(TEST_SERIAL_1)
-                .setStartDate(NOW)
-                .setEndDate(NOW_PLUS_10_YEARS)
-                .setEncryptionRequired()
-                .build());
-    }
-
-    public void testKeyPairGenerator_Initialize_KeySize_Encrypted_Failure() throws Exception {
-        setupPassword();
-
-        try {
-            mGenerator.initialize(1024);
-            fail("KeyPairGenerator should not support setting the key size");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testKeyPairGenerator_Initialize_KeySizeAndSecureRandom_Encrypted_Failure()
-            throws Exception {
-        setupPassword();
-
-        try {
-            mGenerator.initialize(1024, new SecureRandom());
-            fail("KeyPairGenerator should not support setting the key size");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testKeyPairGenerator_Initialize_ParamsAndSecureRandom_Encrypted_Failure()
-            throws Exception {
-        setupPassword();
-
-        mGenerator.initialize(
-                new KeyPairGeneratorSpec.Builder(getContext())
-                        .setAlias(TEST_ALIAS_1)
-                        .setKeyType("RSA")
-                        .setKeySize(1024)
-                        .setSubject(TEST_DN_1)
-                        .setSerialNumber(TEST_SERIAL_1)
-                        .setStartDate(NOW)
-                        .setEndDate(NOW_PLUS_10_YEARS)
-                        .setEncryptionRequired()
-                        .build(),
-                new SecureRandom());
-    }
-
-    public void testKeyPairGenerator_GenerateKeyPair_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                .setAlias(TEST_ALIAS_1)
-                .setSubject(TEST_DN_1)
-                .setSerialNumber(TEST_SERIAL_1)
-                .setStartDate(NOW)
-                .setEndDate(NOW_PLUS_10_YEARS)
-                .setEncryptionRequired()
-                .build());
-
-        final KeyPair pair = mGenerator.generateKeyPair();
-        assertNotNull("The KeyPair returned should not be null", pair);
-
-        assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW,
-                NOW_PLUS_10_YEARS);
-    }
-
-    public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception {
-        KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
-        generator.initialize(new KeyGenParameterSpec.Builder(
-                TEST_ALIAS_1,
-                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
-                .setCertificateSubject(TEST_DN_1)
-                .setCertificateSerialNumber(TEST_SERIAL_1)
-                .setCertificateNotBefore(NOW)
-                .setCertificateNotAfter(NOW_PLUS_10_YEARS)
-                .setDigests(KeyProperties.DIGEST_SHA256)
-                .build());
-
-        final KeyPair pair = generator.generateKeyPair();
-        assertNotNull("The KeyPair returned should not be null", pair);
-
-        assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW,
-                NOW_PLUS_10_YEARS);
-    }
-
-    public void testKeyPairGenerator_Legacy_GenerateKeyPair_EC_Unencrypted_Success()
-            throws Exception {
-        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                .setAlias(TEST_ALIAS_1)
-                .setKeyType("EC")
-                .setSubject(TEST_DN_1)
-                .setSerialNumber(TEST_SERIAL_1)
-                .setStartDate(NOW)
-                .setEndDate(NOW_PLUS_10_YEARS)
-                .build());
-
-        final KeyPair pair = mGenerator.generateKeyPair();
-        assertNotNull("The KeyPair returned should not be null", pair);
-
-        assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW,
-                NOW_PLUS_10_YEARS);
-    }
-
-    public void testKeyPairGenerator_GenerateKeyPair_EC_P521_Unencrypted_Success() throws Exception {
-        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                .setAlias(TEST_ALIAS_1)
-                .setKeyType("EC")
-                .setKeySize(521)
-                .setSubject(TEST_DN_1)
-                .setSerialNumber(TEST_SERIAL_1)
-                .setStartDate(NOW)
-                .setEndDate(NOW_PLUS_10_YEARS)
-                .build());
-
-        final KeyPair pair = mGenerator.generateKeyPair();
-        assertNotNull("The KeyPair returned should not be null", pair);
-
-        assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 521, null, TEST_DN_1, TEST_SERIAL_1, NOW,
-                NOW_PLUS_10_YEARS);
-    }
-
-    public void testKeyPairGenerator_GenerateKeyPair_RSA_Unencrypted_Success() throws Exception {
-        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                .setAlias(TEST_ALIAS_1)
-                .setSubject(TEST_DN_1)
-                .setSerialNumber(TEST_SERIAL_1)
-                .setStartDate(NOW)
-                .setEndDate(NOW_PLUS_10_YEARS)
-                .build());
-
-        final KeyPair pair = mGenerator.generateKeyPair();
-        assertNotNull("The KeyPair returned should not be null", pair);
-
-        assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW,
-                NOW_PLUS_10_YEARS);
-    }
-
-    public void testKeyPairGenerator_GenerateKeyPair_RSA_WithParams_Unencrypted_Success()
-            throws Exception {
-        AlgorithmParameterSpec spec = new RSAKeyGenParameterSpec(1024, BigInteger.valueOf(3L));
-        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                .setAlias(TEST_ALIAS_1)
-                .setKeySize(1024)
-                .setAlgorithmParameterSpec(spec)
-                .setSubject(TEST_DN_1)
-                .setSerialNumber(TEST_SERIAL_1)
-                .setStartDate(NOW)
-                .setEndDate(NOW_PLUS_10_YEARS)
-                .build());
-
-        final KeyPair pair = mGenerator.generateKeyPair();
-        assertNotNull("The KeyPair returned should not be null", pair);
-
-        assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 1024, spec, TEST_DN_1, TEST_SERIAL_1, NOW,
-                NOW_PLUS_10_YEARS);
-    }
-
-    public void testKeyPairGenerator_GenerateKeyPair_Replaced_Success() throws Exception {
-        // Generate the first key
-        {
-            mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                    .setAlias(TEST_ALIAS_1)
-                    .setSubject(TEST_DN_1)
-                    .setSerialNumber(TEST_SERIAL_1)
-                    .setStartDate(NOW)
-                    .setEndDate(NOW_PLUS_10_YEARS)
-                    .build());
-            final KeyPair pair1 = mGenerator.generateKeyPair();
-            assertNotNull("The KeyPair returned should not be null", pair1);
-            assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1,
-                    NOW, NOW_PLUS_10_YEARS);
-        }
-
-        // Replace the original key
-        {
-            mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                    .setAlias(TEST_ALIAS_2)
-                    .setSubject(TEST_DN_2)
-                    .setSerialNumber(TEST_SERIAL_2)
-                    .setStartDate(NOW)
-                    .setEndDate(NOW_PLUS_10_YEARS)
-                    .build());
-            final KeyPair pair2 = mGenerator.generateKeyPair();
-            assertNotNull("The KeyPair returned should not be null", pair2);
-            assertKeyPairCorrect(pair2, TEST_ALIAS_2, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2,
-                    NOW, NOW_PLUS_10_YEARS);
-        }
-    }
-
-    public void testKeyPairGenerator_GenerateKeyPair_Replaced_UnencryptedToEncrypted_Success()
-            throws Exception {
-        // Generate the first key
-        {
-            mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                    .setAlias(TEST_ALIAS_1)
-                    .setSubject(TEST_DN_1)
-                    .setSerialNumber(TEST_SERIAL_1)
-                    .setStartDate(NOW)
-                    .setEndDate(NOW_PLUS_10_YEARS)
-                    .build());
-            final KeyPair pair1 = mGenerator.generateKeyPair();
-            assertNotNull("The KeyPair returned should not be null", pair1);
-            assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1,
-                    NOW, NOW_PLUS_10_YEARS);
-        }
-
-        // Attempt to replace previous key
-        {
-            mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
-                    .setAlias(TEST_ALIAS_1)
-                    .setSubject(TEST_DN_2)
-                    .setSerialNumber(TEST_SERIAL_2)
-                    .setStartDate(NOW)
-                    .setEndDate(NOW_PLUS_10_YEARS)
-                    .setEncryptionRequired()
-                    .build());
-            try {
-                mGenerator.generateKeyPair();
-                fail("Should not be able to generate encrypted key while not initialized");
-            } catch (IllegalStateException expected) {
-            }
-
-            assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
-            assertTrue(mAndroidKeyStore.isUnlocked());
-
-            final KeyPair pair2 = mGenerator.generateKeyPair();
-            assertNotNull("The KeyPair returned should not be null", pair2);
-            assertKeyPairCorrect(pair2, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2,
-                    NOW, NOW_PLUS_10_YEARS);
-        }
-    }
-
-    private void assertKeyPairCorrect(KeyPair pair, String alias, String keyType, int keySize,
-            AlgorithmParameterSpec spec, X500Principal dn, BigInteger serial, Date start, Date end)
-            throws Exception {
-        final PublicKey pubKey = pair.getPublic();
-        assertNotNull("The PublicKey for the KeyPair should be not null", pubKey);
-        assertEquals(keyType, pubKey.getAlgorithm());
-
-        if ("EC".equalsIgnoreCase(keyType)) {
-            assertEquals("Curve should be what was specified during initialization", keySize,
-                    ((ECPublicKey) pubKey).getParams().getCurve().getField().getFieldSize());
-        } else if ("RSA".equalsIgnoreCase(keyType)) {
-            RSAPublicKey rsaPubKey = (RSAPublicKey) pubKey;
-            assertEquals("Modulus size should be what is specified during initialization",
-                    (keySize + 7) & ~7, (rsaPubKey.getModulus().bitLength() + 7) & ~7);
-            if (spec != null) {
-                RSAKeyGenParameterSpec params = (RSAKeyGenParameterSpec) spec;
-                assertEquals((keySize + 7) & ~7, (params.getKeysize() + 7) & ~7);
-                assertEquals(params.getPublicExponent(), rsaPubKey.getPublicExponent());
-            }
-        }
-
-        final PrivateKey privKey = pair.getPrivate();
-        assertNotNull("The PrivateKey for the KeyPair should be not null", privKey);
-        assertEquals(keyType, privKey.getAlgorithm());
-
-        if ("EC".equalsIgnoreCase(keyType)) {
-            assertTrue("EC private key must be instanceof ECKey: " + privKey.getClass().getName(),
-                    privKey instanceof ECKey);
-            assertEquals("Private and public key must have the same EC parameters",
-                    ((ECKey) pubKey).getParams(), ((ECKey) privKey).getParams());
-        } else if ("RSA".equalsIgnoreCase(keyType)) {
-            assertTrue("RSA private key must be instance of RSAKey: "
-                    + privKey.getClass().getName(),
-                    privKey instanceof RSAKey);
-            assertEquals("Private and public key must have the same RSA modulus",
-                    ((RSAKey) pubKey).getModulus(), ((RSAKey) privKey).getModulus());
-        }
-
-        final byte[] userCertBytes = mAndroidKeyStore.get(Credentials.USER_CERTIFICATE + alias);
-        assertNotNull("The user certificate should exist for the generated entry", userCertBytes);
-
-        final CertificateFactory cf = CertificateFactory.getInstance("X.509");
-        final Certificate userCert =
-                cf.generateCertificate(new ByteArrayInputStream(userCertBytes));
-
-        assertTrue("Certificate should be in X.509 format", userCert instanceof X509Certificate);
-
-        final X509Certificate x509userCert = (X509Certificate) userCert;
-
-        assertEquals(
-                "Public key used to sign certificate should have the same algorithm as in KeyPair",
-                pubKey.getAlgorithm(), x509userCert.getPublicKey().getAlgorithm());
-
-        assertEquals("PublicKey used to sign certificate should match one returned in KeyPair",
-                pubKey,
-                AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
-                        Credentials.USER_PRIVATE_KEY + alias,
-                        KeyStore.UID_SELF,
-                        x509userCert.getPublicKey().getAlgorithm(),
-                        x509userCert.getPublicKey().getEncoded()));
-
-        assertEquals("The Subject DN should be the one passed into the params", dn,
-                x509userCert.getSubjectDN());
-
-        assertEquals("The Issuer DN should be the same as the Subject DN", dn,
-                x509userCert.getIssuerDN());
-
-        assertEquals("The Serial should be the one passed into the params", serial,
-                x509userCert.getSerialNumber());
-
-        assertDateEquals("The notBefore date should be the one passed into the params", start,
-                x509userCert.getNotBefore());
-
-        assertDateEquals("The notAfter date should be the one passed into the params", end,
-                x509userCert.getNotAfter());
-
-        // Assert that the cert's signature verifies using the public key from generated KeyPair
-        x509userCert.verify(pubKey);
-        // Assert that the cert's signature verifies using the public key from the cert itself.
-        x509userCert.verify(x509userCert.getPublicKey());
-
-        final byte[] caCerts = mAndroidKeyStore.get(Credentials.CA_CERTIFICATE + alias);
-        assertNull("A list of CA certificates should not exist for the generated entry", caCerts);
-
-        ExportResult exportResult = mAndroidKeyStore.exportKey(
-                Credentials.USER_PRIVATE_KEY + alias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
-        assertEquals(KeyStore.NO_ERROR, exportResult.resultCode);
-        final byte[] pubKeyBytes = exportResult.exportData;
-        assertNotNull("The keystore should return the public key for the generated key",
-                pubKeyBytes);
-        assertTrue("Public key X.509 format should be as expected",
-                Arrays.equals(pubKey.getEncoded(), pubKeyBytes));
-    }
-
-    private static void assertDateEquals(String message, Date date1, Date date2) throws Exception {
-        SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
-
-        String result1 = formatter.format(date1);
-        String result2 = formatter.format(date2);
-
-        assertEquals(message, result1, result2);
-    }
-}
diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
deleted file mode 100644
index aa718dc..0000000
--- a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
+++ /dev/null
@@ -1,2210 +0,0 @@
-/*
- * Copyright (C) 2012 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.security.keystore;
-
-import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
-
-import com.android.org.conscrypt.NativeConstants;
-
-import android.security.Credentials;
-import android.security.KeyStore;
-import android.security.KeyStoreParameter;
-import android.test.AndroidTestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.math.BigInteger;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyStore.Entry;
-import java.security.KeyStore.PrivateKeyEntry;
-import java.security.KeyStore.TrustedCertificateEntry;
-import java.security.KeyStoreException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.RSAKey;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.x500.X500Principal;
-
-public class AndroidKeyStoreTest extends AndroidTestCase {
-    private android.security.KeyStore mAndroidKeyStore;
-
-    private java.security.KeyStore mKeyStore;
-
-    private static final String TEST_ALIAS_1 = "test1";
-
-    private static final String TEST_ALIAS_2 = "test2";
-
-    private static final String TEST_ALIAS_3 = "test3";
-
-    private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
-
-    private static final X500Principal TEST_DN_2 = new X500Principal("CN=test2");
-
-    private static final BigInteger TEST_SERIAL_1 = BigInteger.ONE;
-
-    private static final BigInteger TEST_SERIAL_2 = BigInteger.valueOf(2L);
-
-    private static final long NOW_MILLIS = System.currentTimeMillis();
-
-    /* We have to round this off because X509v3 doesn't store milliseconds. */
-    private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
-
-    @SuppressWarnings("deprecation")
-    private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
-
-    /*
-     * The keys and certificates below are generated with:
-     *
-     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
-     * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req
-     * mkdir -p demoCA/newcerts
-     * touch demoCA/index.txt
-     * echo "01" > demoCA/serial
-     * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
-     */
-
-    /**
-     * Generated from above and converted with:
-     *
-     * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
-     */
-    private static final byte[] FAKE_RSA_CA_1 = {
-            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82,
-            (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
-            (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a,
-            (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, (byte) 0x55, (byte) 0x48, (byte) 0x0a,
-            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
-            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
-            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31,
-            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53,
-            (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43,
-            (byte) 0x41, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d,
-            (byte) 0x4d, (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61,
-            (byte) 0x69, (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65,
-            (byte) 0x77, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12,
-            (byte) 0x41, (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69,
-            (byte) 0x64, (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74,
-            (byte) 0x20, (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73,
-            (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32,
-            (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x34, (byte) 0x31, (byte) 0x36,
-            (byte) 0x35, (byte) 0x35, (byte) 0x34, (byte) 0x34, (byte) 0x5a, (byte) 0x17,
-            (byte) 0x0d, (byte) 0x32, (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31,
-            (byte) 0x32, (byte) 0x31, (byte) 0x36, (byte) 0x35, (byte) 0x35, (byte) 0x34,
-            (byte) 0x34, (byte) 0x5a, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b,
-            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31,
-            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41,
-            (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d,
-            (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69,
-            (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77,
-            (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41,
-            (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64,
-            (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20,
-            (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30,
-            (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
-            (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
-            (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03,
-            (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89,
-            (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa3, (byte) 0x72,
-            (byte) 0xab, (byte) 0xd0, (byte) 0xe4, (byte) 0xad, (byte) 0x2f, (byte) 0xe7,
-            (byte) 0xe2, (byte) 0x79, (byte) 0x07, (byte) 0x36, (byte) 0x3d, (byte) 0x0c,
-            (byte) 0x8d, (byte) 0x42, (byte) 0x9a, (byte) 0x0a, (byte) 0x33, (byte) 0x64,
-            (byte) 0xb3, (byte) 0xcd, (byte) 0xb2, (byte) 0xd7, (byte) 0x3a, (byte) 0x42,
-            (byte) 0x06, (byte) 0x77, (byte) 0x45, (byte) 0x29, (byte) 0xe9, (byte) 0xcb,
-            (byte) 0xb7, (byte) 0x4a, (byte) 0xd6, (byte) 0xee, (byte) 0xad, (byte) 0x01,
-            (byte) 0x91, (byte) 0x9b, (byte) 0x0c, (byte) 0x59, (byte) 0xa1, (byte) 0x03,
-            (byte) 0xfa, (byte) 0xf0, (byte) 0x5a, (byte) 0x7c, (byte) 0x4f, (byte) 0xf7,
-            (byte) 0x8d, (byte) 0x36, (byte) 0x0f, (byte) 0x1f, (byte) 0x45, (byte) 0x7d,
-            (byte) 0x1b, (byte) 0x31, (byte) 0xa1, (byte) 0x35, (byte) 0x0b, (byte) 0x00,
-            (byte) 0xed, (byte) 0x7a, (byte) 0xb6, (byte) 0xc8, (byte) 0x4e, (byte) 0xa9,
-            (byte) 0x86, (byte) 0x4c, (byte) 0x7b, (byte) 0x99, (byte) 0x57, (byte) 0x41,
-            (byte) 0x12, (byte) 0xef, (byte) 0x6b, (byte) 0xbc, (byte) 0x3d, (byte) 0x60,
-            (byte) 0xf2, (byte) 0x99, (byte) 0x1a, (byte) 0xcd, (byte) 0xed, (byte) 0x56,
-            (byte) 0xa4, (byte) 0xe5, (byte) 0x36, (byte) 0x9f, (byte) 0x24, (byte) 0x1f,
-            (byte) 0xdc, (byte) 0x89, (byte) 0x40, (byte) 0xc8, (byte) 0x99, (byte) 0x92,
-            (byte) 0xab, (byte) 0x4a, (byte) 0xb5, (byte) 0x61, (byte) 0x45, (byte) 0x62,
-            (byte) 0xff, (byte) 0xa3, (byte) 0x45, (byte) 0x65, (byte) 0xaf, (byte) 0xf6,
-            (byte) 0x27, (byte) 0x30, (byte) 0x51, (byte) 0x0e, (byte) 0x0e, (byte) 0xeb,
-            (byte) 0x79, (byte) 0x0c, (byte) 0xbe, (byte) 0xb3, (byte) 0x0a, (byte) 0x6f,
-            (byte) 0x29, (byte) 0x06, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x51,
-            (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3,
-            (byte) 0x81, (byte) 0xb1, (byte) 0x30, (byte) 0x81, (byte) 0xae, (byte) 0x30,
-            (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e,
-            (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x33, (byte) 0x05,
-            (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, (byte) 0xc7, (byte) 0xf9,
-            (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, (byte) 0x8f, (byte) 0x6d,
-            (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, (byte) 0x5d, (byte) 0x51,
-            (byte) 0x30, (byte) 0x7f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d,
-            (byte) 0x23, (byte) 0x04, (byte) 0x78, (byte) 0x30, (byte) 0x76, (byte) 0x80,
-            (byte) 0x14, (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f,
-            (byte) 0x60, (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73,
-            (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97,
-            (byte) 0x8e, (byte) 0x5d, (byte) 0x51, (byte) 0xa1, (byte) 0x53, (byte) 0xa4,
-            (byte) 0x51, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
-            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
-            (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
-            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
-            (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
-            (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
-            (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
-            (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
-            (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
-            (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
-            (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x82, (byte) 0x09,
-            (byte) 0x00, (byte) 0xe1, (byte) 0x6a, (byte) 0xa2, (byte) 0xf4, (byte) 0x2e,
-            (byte) 0x55, (byte) 0x48, (byte) 0x0a, (byte) 0x30, (byte) 0x0c, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05,
-            (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30,
-            (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
-            (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05,
-            (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00,
-            (byte) 0x8c, (byte) 0x30, (byte) 0x42, (byte) 0xfa, (byte) 0xeb, (byte) 0x1a,
-            (byte) 0x26, (byte) 0xeb, (byte) 0xda, (byte) 0x56, (byte) 0x32, (byte) 0xf2,
-            (byte) 0x9d, (byte) 0xa5, (byte) 0x24, (byte) 0xd8, (byte) 0x3a, (byte) 0xda,
-            (byte) 0x30, (byte) 0xa6, (byte) 0x8b, (byte) 0x46, (byte) 0xfe, (byte) 0xfe,
-            (byte) 0xdb, (byte) 0xf1, (byte) 0xe6, (byte) 0xe1, (byte) 0x7c, (byte) 0x1b,
-            (byte) 0xe7, (byte) 0x77, (byte) 0x00, (byte) 0xa1, (byte) 0x1c, (byte) 0x19,
-            (byte) 0x17, (byte) 0x73, (byte) 0xb0, (byte) 0xf0, (byte) 0x9d, (byte) 0xf3,
-            (byte) 0x4f, (byte) 0xb6, (byte) 0xbc, (byte) 0xc7, (byte) 0x47, (byte) 0x85,
-            (byte) 0x2a, (byte) 0x4a, (byte) 0xa1, (byte) 0xa5, (byte) 0x58, (byte) 0xf5,
-            (byte) 0xc5, (byte) 0x1a, (byte) 0x51, (byte) 0xb1, (byte) 0x04, (byte) 0x80,
-            (byte) 0xee, (byte) 0x3a, (byte) 0xec, (byte) 0x2f, (byte) 0xe1, (byte) 0xfd,
-            (byte) 0x58, (byte) 0xeb, (byte) 0xed, (byte) 0x82, (byte) 0x9e, (byte) 0x38,
-            (byte) 0xa3, (byte) 0x24, (byte) 0x75, (byte) 0xf7, (byte) 0x3e, (byte) 0xc2,
-            (byte) 0xc5, (byte) 0x27, (byte) 0xeb, (byte) 0x6f, (byte) 0x7b, (byte) 0x50,
-            (byte) 0xda, (byte) 0x43, (byte) 0xdc, (byte) 0x3b, (byte) 0x0b, (byte) 0x6f,
-            (byte) 0x78, (byte) 0x8f, (byte) 0xb0, (byte) 0x66, (byte) 0xe1, (byte) 0x12,
-            (byte) 0x87, (byte) 0x5f, (byte) 0x97, (byte) 0x7b, (byte) 0xca, (byte) 0x14,
-            (byte) 0x79, (byte) 0xf7, (byte) 0xe8, (byte) 0x6c, (byte) 0x72, (byte) 0xdb,
-            (byte) 0x91, (byte) 0x65, (byte) 0x17, (byte) 0x54, (byte) 0xe0, (byte) 0x74,
-            (byte) 0x1d, (byte) 0xac, (byte) 0x47, (byte) 0x04, (byte) 0x12, (byte) 0xe0,
-            (byte) 0xc3, (byte) 0x66, (byte) 0x19, (byte) 0x05, (byte) 0x2e, (byte) 0x7e,
-            (byte) 0xf1, (byte) 0x61
-    };
-
-    /**
-     * Generated from above and converted with:
-     *
-     * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
-     */
-    private static final byte[] FAKE_RSA_KEY_1 = new byte[] {
-            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
-            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
-            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
-            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
-            (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
-            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
-            (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
-            (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
-            (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
-            (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
-            (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
-            (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
-            (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
-            (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
-            (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
-            (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
-            (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
-            (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
-            (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
-            (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
-            (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
-            (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
-            (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
-            (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
-            (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
-            (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
-            (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
-            (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
-            (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
-            (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
-            (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
-            (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
-            (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
-            (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
-            (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
-            (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
-            (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
-            (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
-            (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
-            (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
-            (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
-            (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
-            (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
-            (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
-            (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
-            (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
-            (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
-            (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
-            (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
-            (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
-            (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
-            (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
-            (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
-            (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
-            (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
-            (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
-            (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
-            (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
-            (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
-            (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
-            (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
-            (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
-            (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
-            (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
-            (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
-            (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
-            (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
-            (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
-            (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
-            (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
-            (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
-            (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
-            (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
-            (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
-            (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
-            (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
-            (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
-            (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
-            (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
-            (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
-            (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
-            (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
-            (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
-            (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
-            (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
-            (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
-            (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
-            (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
-            (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
-            (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
-            (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
-            (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
-            (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
-            (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
-            (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
-            (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
-            (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
-            (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
-            (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
-            (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
-            (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
-            (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
-            (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
-            (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
-            (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
-            (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
-    };
-
-    /**
-     * Generated from above and converted with:
-     *
-     * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
-     */
-    private static final byte[] FAKE_RSA_USER_1 = new byte[] {
-            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x95, (byte) 0x30, (byte) 0x82,
-            (byte) 0x01, (byte) 0xfe, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
-            (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
-            (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
-            (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05,
-            (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
-            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
-            (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
-            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
-            (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
-            (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
-            (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
-            (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
-            (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
-            (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
-            (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30, (byte) 0x1e,
-            (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32, (byte) 0x30, (byte) 0x38,
-            (byte) 0x31, (byte) 0x34, (byte) 0x32, (byte) 0x33, (byte) 0x32, (byte) 0x35,
-            (byte) 0x34, (byte) 0x38, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32,
-            (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x32, (byte) 0x32,
-            (byte) 0x33, (byte) 0x32, (byte) 0x35, (byte) 0x34, (byte) 0x38, (byte) 0x5a,
-            (byte) 0x30, (byte) 0x55, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09,
-            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13,
-            (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
-            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08,
-            (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, (byte) 0x1b,
-            (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, (byte) 0x64,
-            (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, (byte) 0x54,
-            (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, (byte) 0x61,
-            (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x31, (byte) 0x1c, (byte) 0x30,
-            (byte) 0x1a, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03,
-            (byte) 0x13, (byte) 0x13, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
-            (byte) 0x65, (byte) 0x72, (byte) 0x31, (byte) 0x2e, (byte) 0x65, (byte) 0x78,
-            (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e,
-            (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x81, (byte) 0x9f,
-            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
-            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
-            (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d,
-            (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81,
-            (byte) 0x81, (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6,
-            (byte) 0x5b, (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c,
-            (byte) 0x66, (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86,
-            (byte) 0x8a, (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3,
-            (byte) 0x02, (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08,
-            (byte) 0xf3, (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04,
-            (byte) 0x6d, (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f,
-            (byte) 0x67, (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c,
-            (byte) 0xcb, (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30,
-            (byte) 0xe2, (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5,
-            (byte) 0x79, (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b,
-            (byte) 0xce, (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb,
-            (byte) 0x08, (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff,
-            (byte) 0x3b, (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9,
-            (byte) 0xc4, (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29,
-            (byte) 0x0d, (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b,
-            (byte) 0x23, (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78,
-            (byte) 0x08, (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5,
-            (byte) 0xf1, (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19,
-            (byte) 0xb4, (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03,
-            (byte) 0x16, (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce,
-            (byte) 0x9e, (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03,
-            (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x7b, (byte) 0x30,
-            (byte) 0x79, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00,
-            (byte) 0x30, (byte) 0x2c, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86,
-            (byte) 0x48, (byte) 0x01, (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01,
-            (byte) 0x0d, (byte) 0x04, (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f,
-            (byte) 0x70, (byte) 0x65, (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c,
-            (byte) 0x20, (byte) 0x47, (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72,
-            (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43,
-            (byte) 0x65, (byte) 0x72, (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69,
-            (byte) 0x63, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d,
-            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04,
-            (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x32, (byte) 0xa1, (byte) 0x1e,
-            (byte) 0x6b, (byte) 0x69, (byte) 0x04, (byte) 0xfe, (byte) 0xb3, (byte) 0xcd,
-            (byte) 0xf8, (byte) 0xbb, (byte) 0x14, (byte) 0xcd, (byte) 0xff, (byte) 0xd4,
-            (byte) 0x16, (byte) 0xc3, (byte) 0xab, (byte) 0x44, (byte) 0x2f, (byte) 0x30,
-            (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23,
-            (byte) 0x04, (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14,
-            (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60,
-            (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c,
-            (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e,
-            (byte) 0x5d, (byte) 0x51, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
-            (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
-            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03,
-            (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x46, (byte) 0x42, (byte) 0xef,
-            (byte) 0x56, (byte) 0x89, (byte) 0x78, (byte) 0x90, (byte) 0x38, (byte) 0x24,
-            (byte) 0x9f, (byte) 0x8c, (byte) 0x7a, (byte) 0xce, (byte) 0x7a, (byte) 0xa5,
-            (byte) 0xb5, (byte) 0x1e, (byte) 0x74, (byte) 0x96, (byte) 0x34, (byte) 0x49,
-            (byte) 0x8b, (byte) 0xed, (byte) 0x44, (byte) 0xb3, (byte) 0xc9, (byte) 0x05,
-            (byte) 0xd7, (byte) 0x48, (byte) 0x55, (byte) 0x52, (byte) 0x59, (byte) 0x15,
-            (byte) 0x0b, (byte) 0xaa, (byte) 0x16, (byte) 0x86, (byte) 0xd2, (byte) 0x8e,
-            (byte) 0x16, (byte) 0x99, (byte) 0xe8, (byte) 0x5f, (byte) 0x11, (byte) 0x71,
-            (byte) 0x42, (byte) 0x55, (byte) 0xd1, (byte) 0xc4, (byte) 0x6f, (byte) 0x2e,
-            (byte) 0xa9, (byte) 0x64, (byte) 0x6f, (byte) 0xd8, (byte) 0xfd, (byte) 0x43,
-            (byte) 0x13, (byte) 0x24, (byte) 0xaa, (byte) 0x67, (byte) 0xe6, (byte) 0xf5,
-            (byte) 0xca, (byte) 0x80, (byte) 0x5e, (byte) 0x3a, (byte) 0x3e, (byte) 0xcc,
-            (byte) 0x4f, (byte) 0xba, (byte) 0x87, (byte) 0xe6, (byte) 0xae, (byte) 0xbf,
-            (byte) 0x8f, (byte) 0xd5, (byte) 0x28, (byte) 0x38, (byte) 0x58, (byte) 0x30,
-            (byte) 0x24, (byte) 0xf6, (byte) 0x53, (byte) 0x5b, (byte) 0x41, (byte) 0x53,
-            (byte) 0xe6, (byte) 0x45, (byte) 0xbc, (byte) 0xbe, (byte) 0xe6, (byte) 0xbb,
-            (byte) 0x5d, (byte) 0xd8, (byte) 0xa7, (byte) 0xf9, (byte) 0x64, (byte) 0x99,
-            (byte) 0x04, (byte) 0x43, (byte) 0x75, (byte) 0xd7, (byte) 0x2d, (byte) 0x32,
-            (byte) 0x0a, (byte) 0x94, (byte) 0xaf, (byte) 0x06, (byte) 0x34, (byte) 0xae,
-            (byte) 0x46, (byte) 0xbd, (byte) 0xda, (byte) 0x00, (byte) 0x0e, (byte) 0x25,
-            (byte) 0xc2, (byte) 0xf7, (byte) 0xc9, (byte) 0xc3, (byte) 0x65, (byte) 0xd2,
-            (byte) 0x08, (byte) 0x41, (byte) 0x0a, (byte) 0xf3, (byte) 0x72
-    };
-
-    /*
-     * The keys and certificates below are generated with:
-     *
-     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
-     * openssl ecparam -name prime256v1 -out ecparam.pem
-     * openssl req -newkey ec:ecparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req
-     * mkdir -p demoCA/newcerts
-     * touch demoCA/index.txt
-     * echo "01" > demoCA/serial
-     * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
-     */
-
-    /**
-     * Generated from above and converted with:
-     *
-     * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
-     */
-    private static final byte[] FAKE_EC_CA_1 = {
-            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x58, (byte) 0x30, (byte) 0x82,
-            (byte) 0x01, (byte) 0xc1, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
-            (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0xb2,
-            (byte) 0x8c, (byte) 0x04, (byte) 0x95, (byte) 0xeb, (byte) 0x10, (byte) 0xcb,
-            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
-            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
-            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31,
-            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55,
-            (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53,
-            (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74,
-            (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30,
-            (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a,
-            (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65,
-            (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57,
-            (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73,
-            (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c,
-            (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d,
-            (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37,
-            (byte) 0x31, (byte) 0x36, (byte) 0x32, (byte) 0x38, (byte) 0x32, (byte) 0x38,
-            (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30,
-            (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x32,
-            (byte) 0x38, (byte) 0x32, (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x45,
-            (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41,
-            (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a,
-            (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53,
-            (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21,
-            (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74,
-            (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20,
-            (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74,
-            (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20,
-            (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x81, (byte) 0x9f,
-            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
-            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
-            (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d,
-            (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81,
-            (byte) 0x81, (byte) 0x00, (byte) 0xb5, (byte) 0xf6, (byte) 0x08, (byte) 0x0f,
-            (byte) 0xc4, (byte) 0x4d, (byte) 0xe4, (byte) 0x0d, (byte) 0x34, (byte) 0x1d,
-            (byte) 0xe2, (byte) 0x23, (byte) 0x18, (byte) 0x63, (byte) 0x03, (byte) 0xf7,
-            (byte) 0x14, (byte) 0x0e, (byte) 0x98, (byte) 0xcd, (byte) 0x45, (byte) 0x1f,
-            (byte) 0xfe, (byte) 0xfb, (byte) 0x09, (byte) 0x3f, (byte) 0x5d, (byte) 0x36,
-            (byte) 0x3b, (byte) 0x0f, (byte) 0xf9, (byte) 0x5e, (byte) 0x86, (byte) 0x56,
-            (byte) 0x64, (byte) 0xd7, (byte) 0x3f, (byte) 0xae, (byte) 0x33, (byte) 0x09,
-            (byte) 0xd3, (byte) 0xdd, (byte) 0x06, (byte) 0x17, (byte) 0x26, (byte) 0xdc,
-            (byte) 0xa2, (byte) 0x8c, (byte) 0x3c, (byte) 0x65, (byte) 0xed, (byte) 0x03,
-            (byte) 0x82, (byte) 0x78, (byte) 0x9b, (byte) 0xee, (byte) 0xe3, (byte) 0x98,
-            (byte) 0x58, (byte) 0xe1, (byte) 0xf1, (byte) 0xa0, (byte) 0x85, (byte) 0xae,
-            (byte) 0x63, (byte) 0x84, (byte) 0x41, (byte) 0x46, (byte) 0xa7, (byte) 0x4f,
-            (byte) 0xdc, (byte) 0xbb, (byte) 0x1c, (byte) 0x6e, (byte) 0xec, (byte) 0x7b,
-            (byte) 0xd5, (byte) 0xab, (byte) 0x3d, (byte) 0x6a, (byte) 0x05, (byte) 0x58,
-            (byte) 0x0f, (byte) 0x9b, (byte) 0x6a, (byte) 0x67, (byte) 0x4b, (byte) 0xe9,
-            (byte) 0x2a, (byte) 0x6d, (byte) 0x96, (byte) 0x11, (byte) 0x53, (byte) 0x95,
-            (byte) 0x78, (byte) 0xaa, (byte) 0xd1, (byte) 0x91, (byte) 0x4a, (byte) 0xf8,
-            (byte) 0x54, (byte) 0x52, (byte) 0x6d, (byte) 0xb9, (byte) 0xca, (byte) 0x74,
-            (byte) 0x81, (byte) 0xf8, (byte) 0x99, (byte) 0x64, (byte) 0xd1, (byte) 0x4f,
-            (byte) 0x01, (byte) 0x38, (byte) 0x4f, (byte) 0x08, (byte) 0x5c, (byte) 0x31,
-            (byte) 0xcb, (byte) 0x7c, (byte) 0x5c, (byte) 0x78, (byte) 0x5d, (byte) 0x47,
-            (byte) 0xd9, (byte) 0xf0, (byte) 0x1a, (byte) 0xeb, (byte) 0x02, (byte) 0x03,
-            (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x50, (byte) 0x30,
-            (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14,
-            (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa,
-            (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1,
-            (byte) 0xbc, (byte) 0x20, (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4,
-            (byte) 0xfa, (byte) 0xe3, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30,
-            (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, (byte) 0x5e,
-            (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, (byte) 0x9e,
-            (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, (byte) 0x72,
-            (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, (byte) 0x30,
-            (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13,
-            (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01,
-            (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
-            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
-            (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81,
-            (byte) 0x81, (byte) 0x00, (byte) 0xa1, (byte) 0x4a, (byte) 0xe6, (byte) 0xfc,
-            (byte) 0x7f, (byte) 0x17, (byte) 0xaa, (byte) 0x65, (byte) 0x4a, (byte) 0x34,
-            (byte) 0xde, (byte) 0x69, (byte) 0x67, (byte) 0x54, (byte) 0x4d, (byte) 0xa2,
-            (byte) 0xc2, (byte) 0x98, (byte) 0x02, (byte) 0x43, (byte) 0x6a, (byte) 0x0e,
-            (byte) 0x0b, (byte) 0x7f, (byte) 0xa4, (byte) 0x46, (byte) 0xaf, (byte) 0xa4,
-            (byte) 0x65, (byte) 0xa0, (byte) 0xdb, (byte) 0xf1, (byte) 0x5b, (byte) 0xd5,
-            (byte) 0x09, (byte) 0xbc, (byte) 0xee, (byte) 0x37, (byte) 0x51, (byte) 0x19,
-            (byte) 0x36, (byte) 0xc0, (byte) 0x90, (byte) 0xd3, (byte) 0x5f, (byte) 0xf3,
-            (byte) 0x4f, (byte) 0xb9, (byte) 0x08, (byte) 0x45, (byte) 0x0e, (byte) 0x01,
-            (byte) 0x8a, (byte) 0x95, (byte) 0xef, (byte) 0x92, (byte) 0x95, (byte) 0x33,
-            (byte) 0x78, (byte) 0xdd, (byte) 0x90, (byte) 0xbb, (byte) 0xf3, (byte) 0x06,
-            (byte) 0x75, (byte) 0xd0, (byte) 0x66, (byte) 0xe6, (byte) 0xd0, (byte) 0x18,
-            (byte) 0x6e, (byte) 0xeb, (byte) 0x1c, (byte) 0x52, (byte) 0xc3, (byte) 0x2e,
-            (byte) 0x57, (byte) 0x7d, (byte) 0xa9, (byte) 0x03, (byte) 0xdb, (byte) 0xf4,
-            (byte) 0x57, (byte) 0x5f, (byte) 0x6c, (byte) 0x7e, (byte) 0x00, (byte) 0x0d,
-            (byte) 0x8f, (byte) 0xe8, (byte) 0x91, (byte) 0xf7, (byte) 0xae, (byte) 0x24,
-            (byte) 0x35, (byte) 0x07, (byte) 0xb5, (byte) 0x48, (byte) 0x2d, (byte) 0x36,
-            (byte) 0x30, (byte) 0x5d, (byte) 0xe9, (byte) 0x49, (byte) 0x2d, (byte) 0xd1,
-            (byte) 0x5d, (byte) 0xc5, (byte) 0xf4, (byte) 0x33, (byte) 0x77, (byte) 0x3c,
-            (byte) 0x71, (byte) 0xad, (byte) 0x90, (byte) 0x65, (byte) 0xa9, (byte) 0xc1,
-            (byte) 0x0b, (byte) 0x5c, (byte) 0x62, (byte) 0x55, (byte) 0x50, (byte) 0x6f,
-            (byte) 0x9b, (byte) 0xc9, (byte) 0x0d, (byte) 0xee
-    };
-
-    /**
-     * Generated from above and converted with:
-     *
-     * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
-     */
-    private static final byte[] FAKE_EC_KEY_1 = new byte[] {
-            (byte) 0x30, (byte) 0x81, (byte) 0x87, (byte) 0x02, (byte) 0x01, (byte) 0x00,
-            (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86,
-            (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
-            (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d,
-            (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x04, (byte) 0x6d, (byte) 0x30,
-            (byte) 0x6b, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x20,
-            (byte) 0x3a, (byte) 0x8a, (byte) 0x02, (byte) 0xdc, (byte) 0xde, (byte) 0x70,
-            (byte) 0x84, (byte) 0x45, (byte) 0x34, (byte) 0xaf, (byte) 0xbd, (byte) 0xd5,
-            (byte) 0x02, (byte) 0x17, (byte) 0x69, (byte) 0x90, (byte) 0x65, (byte) 0x1e,
-            (byte) 0x87, (byte) 0xf1, (byte) 0x3d, (byte) 0x17, (byte) 0xb6, (byte) 0xf4,
-            (byte) 0x31, (byte) 0x94, (byte) 0x86, (byte) 0x76, (byte) 0x55, (byte) 0xf7,
-            (byte) 0xcc, (byte) 0xba, (byte) 0xa1, (byte) 0x44, (byte) 0x03, (byte) 0x42,
-            (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, (byte) 0x9b,
-            (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, (byte) 0x14,
-            (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, (byte) 0xcd,
-            (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, (byte) 0xf3,
-            (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, (byte) 0x79,
-            (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, (byte) 0xd1,
-            (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, (byte) 0x50,
-            (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, (byte) 0xe6,
-            (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, (byte) 0x3b,
-            (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, (byte) 0x5e,
-            (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, (byte) 0x38
-    };
-
-    /**
-     * Generated from above and converted with:
-     *
-     * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
-     */
-    private static final byte[] FAKE_EC_USER_1 = new byte[] {
-            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x51, (byte) 0x30, (byte) 0x82,
-            (byte) 0x01, (byte) 0xba, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
-            (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
-            (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
-            (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05,
-            (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
-            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
-            (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13,
-            (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d,
-            (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74,
-            (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18,
-            (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e,
-            (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64,
-            (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50,
-            (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64,
-            (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x33,
-            (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, (byte) 0x31, (byte) 0x36,
-            (byte) 0x33, (byte) 0x30, (byte) 0x30, (byte) 0x38, (byte) 0x5a, (byte) 0x17,
-            (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32,
-            (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x33, (byte) 0x30, (byte) 0x30,
-            (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x62, (byte) 0x31, (byte) 0x0b,
-            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31,
-            (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f,
-            (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61,
-            (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f,
-            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c,
-            (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72,
-            (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69,
-            (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20,
-            (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74,
-            (byte) 0x64, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, (byte) 0x12,
-            (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72,
-            (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
-            (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d,
-            (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07,
-            (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02,
-            (byte) 0x01, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
-            (byte) 0xce, (byte) 0x3d, (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x03,
-            (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7,
-            (byte) 0x9b, (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33,
-            (byte) 0x14, (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3,
-            (byte) 0xcd, (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d,
-            (byte) 0xf3, (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f,
-            (byte) 0x79, (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3,
-            (byte) 0xd1, (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf,
-            (byte) 0x50, (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22,
-            (byte) 0xe6, (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68,
-            (byte) 0x3b, (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77,
-            (byte) 0x5e, (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2,
-            (byte) 0x38, (byte) 0xa3, (byte) 0x7b, (byte) 0x30, (byte) 0x79, (byte) 0x30,
-            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13,
-            (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00, (byte) 0x30, (byte) 0x2c,
-            (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01,
-            (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01, (byte) 0x0d, (byte) 0x04,
-            (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f, (byte) 0x70, (byte) 0x65,
-            (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c, (byte) 0x20, (byte) 0x47,
-            (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72, (byte) 0x61, (byte) 0x74,
-            (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43, (byte) 0x65, (byte) 0x72,
-            (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69, (byte) 0x63, (byte) 0x61,
-            (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04,
-            (byte) 0x14, (byte) 0xd5, (byte) 0xc4, (byte) 0x72, (byte) 0xbd, (byte) 0xd2,
-            (byte) 0x4e, (byte) 0x90, (byte) 0x1b, (byte) 0x14, (byte) 0x32, (byte) 0xdb,
-            (byte) 0x03, (byte) 0xae, (byte) 0xfa, (byte) 0x27, (byte) 0x7d, (byte) 0x8d,
-            (byte) 0xe4, (byte) 0x80, (byte) 0x58, (byte) 0x30, (byte) 0x1f, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18,
-            (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b,
-            (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f,
-            (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20,
-            (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3,
-            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
-            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
-            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81,
-            (byte) 0x00, (byte) 0x43, (byte) 0x99, (byte) 0x9f, (byte) 0x67, (byte) 0x08,
-            (byte) 0x43, (byte) 0xd5, (byte) 0x6b, (byte) 0x6f, (byte) 0xd7, (byte) 0x05,
-            (byte) 0xd6, (byte) 0x75, (byte) 0x34, (byte) 0x30, (byte) 0xca, (byte) 0x20,
-            (byte) 0x47, (byte) 0x61, (byte) 0xa1, (byte) 0x89, (byte) 0xb6, (byte) 0xf1,
-            (byte) 0x49, (byte) 0x7b, (byte) 0xd9, (byte) 0xb9, (byte) 0xe8, (byte) 0x1e,
-            (byte) 0x29, (byte) 0x74, (byte) 0x0a, (byte) 0x67, (byte) 0xc0, (byte) 0x7d,
-            (byte) 0xb8, (byte) 0xe6, (byte) 0x39, (byte) 0xa8, (byte) 0x5e, (byte) 0xc3,
-            (byte) 0xb0, (byte) 0xa1, (byte) 0x30, (byte) 0x6a, (byte) 0x1f, (byte) 0x1d,
-            (byte) 0xfc, (byte) 0x11, (byte) 0x59, (byte) 0x0b, (byte) 0xb9, (byte) 0xad,
-            (byte) 0x3a, (byte) 0x4e, (byte) 0x50, (byte) 0x0a, (byte) 0x61, (byte) 0xdb,
-            (byte) 0x75, (byte) 0x6b, (byte) 0xe5, (byte) 0x3f, (byte) 0x8d, (byte) 0xde,
-            (byte) 0x28, (byte) 0x68, (byte) 0xb1, (byte) 0x29, (byte) 0x9a, (byte) 0x18,
-            (byte) 0x8a, (byte) 0xfc, (byte) 0x3f, (byte) 0x13, (byte) 0x93, (byte) 0x29,
-            (byte) 0xed, (byte) 0x22, (byte) 0x7c, (byte) 0xb4, (byte) 0x50, (byte) 0xd5,
-            (byte) 0x4d, (byte) 0x32, (byte) 0x4d, (byte) 0x42, (byte) 0x2b, (byte) 0x29,
-            (byte) 0x97, (byte) 0x86, (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0x25,
-            (byte) 0xf6, (byte) 0xd3, (byte) 0x2a, (byte) 0xd8, (byte) 0xda, (byte) 0x13,
-            (byte) 0x94, (byte) 0x12, (byte) 0x78, (byte) 0x14, (byte) 0x0b, (byte) 0x51,
-            (byte) 0xc0, (byte) 0x45, (byte) 0xb4, (byte) 0x02, (byte) 0x37, (byte) 0x98,
-            (byte) 0x42, (byte) 0x3c, (byte) 0xcb, (byte) 0x2e, (byte) 0xe4, (byte) 0x38,
-            (byte) 0x69, (byte) 0x1b, (byte) 0x72, (byte) 0xf0, (byte) 0xaa, (byte) 0x89,
-            (byte) 0x7e, (byte) 0xde, (byte) 0xb2
-    };
-
-    /**
-     * The amount of time to allow before and after expected time for variance
-     * in timing tests.
-     */
-    private static final long SLOP_TIME_MILLIS = 15000L;
-
-    @Override
-    protected void setUp() throws Exception {
-        mAndroidKeyStore = android.security.KeyStore.getInstance();
-
-        assertTrue(mAndroidKeyStore.reset());
-        assertFalse(mAndroidKeyStore.isUnlocked());
-
-        mKeyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
-    }
-
-    private void setupPassword() {
-        assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
-        assertTrue(mAndroidKeyStore.isUnlocked());
-
-        assertEquals(0, mAndroidKeyStore.list("").length);
-    }
-
-    private void assertAliases(final String[] expectedAliases) throws KeyStoreException {
-        final Enumeration<String> aliases = mKeyStore.aliases();
-        int count = 0;
-
-        final Set<String> expectedSet = new HashSet<String>();
-        expectedSet.addAll(Arrays.asList(expectedAliases));
-
-        while (aliases.hasMoreElements()) {
-            count++;
-            final String alias = aliases.nextElement();
-            assertTrue("The alias should be in the expected set", expectedSet.contains(alias));
-            expectedSet.remove(alias);
-        }
-        assertTrue("The expected set and actual set should be exactly equal", expectedSet.isEmpty());
-        assertEquals("There should be the correct number of keystore entries",
-                expectedAliases.length, count);
-    }
-
-    public void testKeyStore_Aliases_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertAliases(new String[] {});
-
-        assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
-                null));
-
-        assertAliases(new String[] { TEST_ALIAS_1 });
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
-    }
-
-    public void testKeyStore_Aliases_NotInitialized_Encrypted_Failure() throws Exception {
-        setupPassword();
-
-        try {
-            mKeyStore.aliases();
-            fail("KeyStore should throw exception when not initialized");
-        } catch (KeyStoreException success) {
-        }
-    }
-
-    public void testKeyStore_ContainsAliases_PrivateAndCA_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertAliases(new String[] {});
-
-        assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
-                null));
-
-        assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1));
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
-
-        assertFalse("Should not contain unadded certificate alias",
-                mKeyStore.containsAlias(TEST_ALIAS_3));
-    }
-
-    public void testKeyStore_ContainsAliases_CAOnly_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
-    }
-
-    public void testKeyStore_ContainsAliases_NonExistent_Encrypted_Failure() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertFalse("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_DeleteEntry_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        // TEST_ALIAS_1
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        // TEST_ALIAS_2
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        // TEST_ALIAS_3
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
-
-        mKeyStore.deleteEntry(TEST_ALIAS_1);
-
-        assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
-
-        mKeyStore.deleteEntry(TEST_ALIAS_3);
-
-        assertAliases(new String[] { TEST_ALIAS_2 });
-
-        mKeyStore.deleteEntry(TEST_ALIAS_2);
-
-        assertAliases(new String[] { });
-    }
-
-    public void testKeyStore_DeleteEntry_EmptyStore_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        // Should not throw when a non-existent entry is requested for delete.
-        mKeyStore.deleteEntry(TEST_ALIAS_1);
-    }
-
-    public void testKeyStore_DeleteEntry_NonExistent_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        // TEST_ALIAS_1
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        // Should not throw when a non-existent entry is requested for delete.
-        mKeyStore.deleteEntry(TEST_ALIAS_2);
-    }
-
-    public void testKeyStore_GetCertificate_Single_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertAliases(new String[] { TEST_ALIAS_1 });
-
-        assertNull("Certificate should not exist in keystore",
-                mKeyStore.getCertificate(TEST_ALIAS_2));
-
-        Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
-
-        assertNotNull("Retrieved certificate should not be null", retrieved);
-
-        CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        assertEquals("Actual and retrieved certificates should be the same", actual, retrieved);
-    }
-
-    public void testKeyStore_GetCertificate_NonExist_Encrypted_Failure() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertNull("Certificate should not exist in keystore",
-                mKeyStore.getCertificate(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_GetCertificateAlias_CAEntry_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
-                mKeyStore.getCertificateAlias(actual));
-    }
-
-    public void testKeyStore_GetCertificateAlias_PrivateKeyEntry_Encrypted_Success()
-            throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
-        assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
-                mKeyStore.getCertificateAlias(actual));
-    }
-
-    public void testKeyStore_GetCertificateAlias_CAEntry_WithPrivateKeyUsingCA_Encrypted_Success()
-            throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        // Insert TrustedCertificateEntry with CA name
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        // Insert PrivateKeyEntry that uses the same CA
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        assertEquals("Stored certificate alias should be found", TEST_ALIAS_2,
-                mKeyStore.getCertificateAlias(actual));
-    }
-
-    public void testKeyStore_GetCertificateAlias_NonExist_Empty_Encrypted_Failure()
-            throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        assertNull("Stored certificate alias should not be found",
-                mKeyStore.getCertificateAlias(actual));
-    }
-
-    public void testKeyStore_GetCertificateAlias_NonExist_Encrypted_Failure() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
-        assertNull("Stored certificate alias should be found",
-                mKeyStore.getCertificateAlias(userCert));
-    }
-
-    public void testKeyStore_GetCertificateChain_SingleLength_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        CertificateFactory cf = CertificateFactory.getInstance("X.509");
-        Certificate[] expected = new Certificate[2];
-        expected[0] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-        expected[1] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        Certificate[] actual = mKeyStore.getCertificateChain(TEST_ALIAS_1);
-
-        assertNotNull("Returned certificate chain should not be null", actual);
-        assertEquals("Returned certificate chain should be correct size", expected.length,
-                actual.length);
-        assertEquals("First certificate should be user certificate", expected[0], actual[0]);
-        assertEquals("Second certificate should be CA certificate", expected[1], actual[1]);
-
-        // Negative test when keystore is populated.
-        assertNull("Stored certificate alias should not be found",
-                mKeyStore.getCertificateChain(TEST_ALIAS_2));
-    }
-
-    public void testKeyStore_GetCertificateChain_NonExist_Encrypted_Failure() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertNull("Stored certificate alias should not be found",
-                mKeyStore.getCertificateChain(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_GetCreationDate_PrivateKeyEntry_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        Date now = new Date();
-        Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
-
-        Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
-        Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
-
-        assertTrue("Time should be close to current time", actual.before(expectedBefore));
-        assertTrue("Time should be close to current time", actual.after(expectedAfter));
-    }
-
-    public void testKeyStore_GetCreationDate_PrivateKeyEntry_Unencrypted_Success() throws Exception {
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
-        Date now = new Date();
-        Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
-
-        Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
-        Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
-
-        assertTrue("Time should be close to current time", actual.before(expectedBefore));
-        assertTrue("Time should be close to current time", actual.after(expectedAfter));
-    }
-
-    public void testKeyStore_GetCreationDate_CAEntry_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        Date now = new Date();
-        Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
-        assertNotNull("Certificate should be found", actual);
-
-        Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
-        Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
-
-        assertTrue("Time should be close to current time", actual.before(expectedBefore));
-        assertTrue("Time should be close to current time", actual.after(expectedAfter));
-    }
-
-    public void testKeyStore_GetEntry_NullParams_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-        assertNotNull("Entry should exist", entry);
-
-        assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
-
-        PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
-        assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                FAKE_RSA_CA_1);
-    }
-
-    public void testKeyStore_GetEntry_EC_NullParams_Unencrypted_Success() throws Exception {
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_EC_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
-                FAKE_EC_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_EC_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
-        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-        assertNotNull("Entry should exist", entry);
-
-        assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
-
-        PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
-        assertPrivateKeyEntryEquals(keyEntry, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1);
-    }
-
-    public void testKeyStore_GetEntry_RSA_NullParams_Unencrypted_Success() throws Exception {
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
-                FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
-        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-        assertNotNull("Entry should exist", entry);
-
-        assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
-
-        PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
-        assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                FAKE_RSA_CA_1);
-    }
-
-    @SuppressWarnings("unchecked")
-    private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, String keyType, byte[] key,
-            byte[] cert, byte[] ca) throws Exception {
-        KeyFactory keyFact = KeyFactory.getInstance(keyType);
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(key));
-
-        CertificateFactory certFact = CertificateFactory.getInstance("X.509");
-        Certificate expectedCert = certFact.generateCertificate(new ByteArrayInputStream(cert));
-
-        final Collection<Certificate> expectedChain;
-        if (ca != null) {
-            expectedChain = (Collection<Certificate>) certFact
-                    .generateCertificates(new ByteArrayInputStream(ca));
-        } else {
-            expectedChain = null;
-        }
-
-        assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, expectedChain);
-    }
-
-    private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey,
-            Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception {
-        if (expectedKey instanceof ECKey) {
-            assertEquals("Returned PrivateKey should be what we inserted",
-                    ((ECKey) expectedKey).getParams().getCurve(),
-                    ((ECKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
-        } else if (expectedKey instanceof RSAKey) {
-            assertEquals("Returned PrivateKey should be what we inserted",
-                    ((RSAKey) expectedKey).getModulus(),
-                    ((RSAKey) keyEntry.getPrivateKey()).getModulus());
-        }
-
-        assertEquals("Returned Certificate should be what we inserted", expectedCert,
-                keyEntry.getCertificate());
-
-        Certificate[] actualChain = keyEntry.getCertificateChain();
-
-        assertEquals("First certificate in chain should be user cert", expectedCert, actualChain[0]);
-
-        if (expectedChain == null) {
-            assertEquals("Certificate chain should not include CAs", 1, actualChain.length);
-        } else {
-            int i = 1;
-            final Iterator<Certificate> it = expectedChain.iterator();
-            while (it.hasNext()) {
-                assertEquals("CA chain certificate should equal what we put in", it.next(),
-                        actualChain[i++]);
-            }
-        }
-    }
-
-    public void testKeyStore_GetEntry_Nonexistent_NullParams_Encrypted_Failure() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertNull("A non-existent entry should return null",
-                mKeyStore.getEntry(TEST_ALIAS_1, null));
-    }
-
-    public void testKeyStore_GetEntry_Nonexistent_NullParams_Unencrypted_Failure() throws Exception {
-        mKeyStore.load(null, null);
-
-        assertNull("A non-existent entry should return null",
-                mKeyStore.getEntry(TEST_ALIAS_1, null));
-    }
-
-    public void testKeyStore_GetKey_NoPassword_Encrypted_Success() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
-        assertNotNull("Key should exist", key);
-
-        assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
-        assertTrue("Should be a RSAKey", key instanceof RSAKey);
-
-        KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
-        assertEquals("Inserted key should be same as retrieved key",
-                ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
-    }
-
-    public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception {
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
-        Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
-        assertNotNull("Key should exist", key);
-
-        assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
-        assertTrue("Should be a RSAKey", key instanceof RSAKey);
-
-        KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
-        assertEquals("Inserted key should be same as retrieved key",
-                ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
-    }
-
-    public void testKeyStore_GetKey_Certificate_Encrypted_Failure() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
-    }
-
-    public void testKeyStore_GetKey_NonExistent_Encrypted_Failure() throws Exception {
-        setupPassword();
-
-        mKeyStore.load(null, null);
-
-        assertNull("A non-existent entry should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
-    }
-
-    public void testKeyStore_GetProvider_Encrypted_Success() throws Exception {
-        assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName());
-        setupPassword();
-        assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName());
-    }
-
-    public void testKeyStore_GetType_Encrypted_Success() throws Exception {
-        assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType());
-        setupPassword();
-        assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType());
-    }
-
-    public void testKeyStore_IsCertificateEntry_CA_Encrypted_Success() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertTrue("Should return true for CA certificate",
-                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_IsCertificateEntry_PrivateKey_Encrypted_Failure() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertFalse("Should return false for PrivateKeyEntry",
-                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_IsCertificateEntry_NonExist_Encrypted_Failure() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        assertFalse("Should return false for non-existent entry",
-                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_IsCertificateEntry_NonExist_Unencrypted_Failure() throws Exception {
-        mKeyStore.load(null, null);
-
-        assertFalse("Should return false for non-existent entry",
-                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_IsKeyEntry_PrivateKey_Encrypted_Success() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_IsKeyEntry_CA_Encrypted_Failure() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_IsKeyEntry_NonExist_Encrypted_Failure() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        assertFalse("Should return false for non-existent entry",
-                mKeyStore.isKeyEntry(TEST_ALIAS_1));
-    }
-
-    public void testKeyStore_SetCertificate_CA_Encrypted_Success() throws Exception {
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-        final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, actual);
-        assertAliases(new String[] { TEST_ALIAS_1 });
-
-        Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
-
-        assertEquals("Retrieved certificate should be the same as the one inserted", actual,
-                retrieved);
-    }
-
-    public void testKeyStore_SetCertificate_CAExists_Overwrite_Encrypted_Success() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertAliases(new String[] { TEST_ALIAS_1 });
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-        final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        // TODO have separate FAKE_CA for second test
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
-
-        assertAliases(new String[] { TEST_ALIAS_1 });
-    }
-
-    public void testKeyStore_SetCertificate_PrivateKeyExists_Encrypted_Failure() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertAliases(new String[] { TEST_ALIAS_1 });
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-        final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        try {
-            mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
-            fail("Should throw when trying to overwrite a PrivateKey entry with a Certificate");
-        } catch (KeyStoreException success) {
-        }
-    }
-
-    public void testKeyStore_SetEntry_PrivateKeyEntry_Encrypted_Success() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        final Certificate[] expectedChain = new Certificate[2];
-        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
-        mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
-        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-        assertNotNull("Retrieved entry should exist", actualEntry);
-
-        assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                actualEntry instanceof PrivateKeyEntry);
-
-        PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
-        assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
-    }
-
-    public void testKeyStore_SetEntry_PrivateKeyEntry_EC_Unencrypted_Success() throws Exception {
-        mKeyStore.load(null, null);
-
-        KeyFactory keyFact = KeyFactory.getInstance("EC");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_EC_KEY_1));
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        final Certificate[] expectedChain = new Certificate[2];
-        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_USER_1));
-        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_CA_1));
-
-        PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
-        mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
-        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-        assertNotNull("Retrieved entry should exist", actualEntry);
-
-        assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                actualEntry instanceof PrivateKeyEntry);
-
-        PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
-        assertPrivateKeyEntryEquals(actual, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1);
-    }
-
-    public void testKeyStore_SetEntry_PrivateKeyEntry_RSA_Unencrypted_Success() throws Exception {
-        mKeyStore.load(null, null);
-
-        KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        final Certificate[] expectedChain = new Certificate[2];
-        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
-        mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
-        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-        assertNotNull("Retrieved entry should exist", actualEntry);
-
-        assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                actualEntry instanceof PrivateKeyEntry);
-
-        PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
-        assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
-    }
-
-    public void testKeyStore_SetEntry_PrivateKeyEntry_Params_Unencrypted_Failure() throws Exception {
-        mKeyStore.load(null, null);
-
-        KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        final Certificate[] expectedChain = new Certificate[2];
-        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        PrivateKeyEntry entry = new PrivateKeyEntry(expectedKey, expectedChain);
-
-        try {
-            mKeyStore.setEntry(TEST_ALIAS_1, entry,
-                    new KeyStoreParameter.Builder(getContext())
-                    .setEncryptionRequired(true)
-                    .build());
-            fail("Shouldn't be able to insert encrypted entry when KeyStore uninitialized");
-        } catch (KeyStoreException expected) {
-        }
-
-        assertNull(mKeyStore.getEntry(TEST_ALIAS_1, null));
-    }
-
-    public void
-            testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_PrivateKeyEntry_Encrypted_Success()
-            throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        final KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        // Start with PrivateKeyEntry
-        {
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
-            final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-            PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
-            mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-
-            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                    actualEntry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
-            assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                    FAKE_RSA_CA_1);
-        }
-
-        // TODO make entirely new test vector for the overwrite
-        // Replace with PrivateKeyEntry
-        {
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
-            final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-            PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
-            mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-
-            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                    actualEntry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
-            assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                    FAKE_RSA_CA_1);
-        }
-    }
-
-    public void testKeyStore_SetEntry_CAEntry_Overwrites_PrivateKeyEntry_Encrypted_Success()
-            throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        // Start with TrustedCertificateEntry
-        {
-            final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-            TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-            assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
-                    actualEntry instanceof TrustedCertificateEntry);
-            TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
-            assertEquals("Stored and retrieved certificates should be the same",
-                    expectedCertEntry.getTrustedCertificate(),
-                    actualCertEntry.getTrustedCertificate());
-        }
-
-        // Replace with PrivateKeyEntry
-        {
-            KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-            final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-            PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                    actualEntry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
-            assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                    FAKE_RSA_CA_1);
-        }
-    }
-
-    public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_CAEntry_Encrypted_Success()
-            throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        // Start with PrivateKeyEntry
-        {
-            KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-            final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-            expectedChain[1] = caCert;
-
-            PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                    actualEntry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
-            assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                    FAKE_RSA_CA_1);
-        }
-
-        // Replace with TrustedCertificateEntry
-        {
-            TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-            assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
-                    actualEntry instanceof TrustedCertificateEntry);
-            TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
-            assertEquals("Stored and retrieved certificates should be the same",
-                    expectedCertEntry.getTrustedCertificate(),
-                    actualCertEntry.getTrustedCertificate());
-        }
-    }
-
-    public
-            void
-            testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_ShortPrivateKeyEntry_Encrypted_Success()
-            throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        // Start with PrivateKeyEntry
-        {
-            KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-            final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-            expectedChain[1] = caCert;
-
-            PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                    actualEntry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
-            assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                    FAKE_RSA_CA_1);
-        }
-
-        // Replace with PrivateKeyEntry that has no chain
-        {
-            KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-            final Certificate[] expectedChain = new Certificate[1];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
-            PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                    actualEntry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
-            assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                    null);
-        }
-    }
-
-    public void testKeyStore_SetEntry_CAEntry_Overwrites_CAEntry_Encrypted_Success()
-            throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        // Insert TrustedCertificateEntry
-        {
-            final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-            TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-            assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
-                    actualEntry instanceof TrustedCertificateEntry);
-            TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
-            assertEquals("Stored and retrieved certificates should be the same",
-                    expectedCertEntry.getTrustedCertificate(),
-                    actualCertEntry.getTrustedCertificate());
-        }
-
-        // Replace with TrustedCertificateEntry of USER
-        {
-            final Certificate userCert = f
-                    .generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
-            TrustedCertificateEntry expectedUserEntry = new TrustedCertificateEntry(userCert);
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedUserEntry, null);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-            assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
-                    actualEntry instanceof TrustedCertificateEntry);
-            TrustedCertificateEntry actualUserEntry = (TrustedCertificateEntry) actualEntry;
-            assertEquals("Stored and retrieved certificates should be the same",
-                    expectedUserEntry.getTrustedCertificate(),
-                    actualUserEntry.getTrustedCertificate());
-        }
-    }
-
-    public void testKeyStore_SetKeyEntry_ProtectedKey_Encrypted_Failure() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-        final Certificate[] chain = new Certificate[2];
-        chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-        chain[1] = caCert;
-
-        try {
-            mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, "foo".toCharArray(), chain);
-            fail("Should fail when a password is specified");
-        } catch (KeyStoreException success) {
-        }
-    }
-
-    public void testKeyStore_SetKeyEntry_Encrypted_Success() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-        final Certificate[] chain = new Certificate[2];
-        chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-        chain[1] = caCert;
-
-        mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
-
-        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-        assertNotNull("Retrieved entry should exist", actualEntry);
-
-        assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                actualEntry instanceof PrivateKeyEntry);
-
-        PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
-        assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
-    }
-
-    public void testKeyStore_SetKeyEntry_Replaced_Encrypted_Success() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
-        // Insert initial key
-        {
-            KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-            final Certificate[] chain = new Certificate[2];
-            chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-            chain[1] = caCert;
-
-            mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-
-            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                    actualEntry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
-            assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                    FAKE_RSA_CA_1);
-        }
-
-        // TODO make a separate key
-        // Replace key
-        {
-            KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-            final Certificate[] chain = new Certificate[2];
-            chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-            chain[1] = caCert;
-
-            mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
-
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-            assertNotNull("Retrieved entry should exist", actualEntry);
-
-            assertTrue("Retrieved entry should be of type PrivateKeyEntry",
-                    actualEntry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
-            assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
-                    FAKE_RSA_CA_1);
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    private static X509Certificate generateCertificate(android.security.KeyStore keyStore,
-            String alias, BigInteger serialNumber, X500Principal subjectDN, Date notBefore,
-            Date notAfter) throws Exception {
-        final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
-
-        KeyPair keyPair = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
-                keyStore, privateKeyAlias, KeyStore.UID_SELF);
-
-        final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
-        certGen.setPublicKey(keyPair.getPublic());
-        certGen.setSerialNumber(serialNumber);
-        certGen.setSubjectDN(subjectDN);
-        certGen.setIssuerDN(subjectDN);
-        certGen.setNotBefore(notBefore);
-        certGen.setNotAfter(notAfter);
-        certGen.setSignatureAlgorithm("sha1WithRSA");
-
-        final X509Certificate cert = certGen.generate(keyPair.getPrivate());
-
-        return cert;
-    }
-
-    public void testKeyStore_SetKeyEntry_ReplacedChain_Encrypted_Success() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        // Create key #1
-        {
-            final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
-            assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
-                    NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
-
-            Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
-
-            assertTrue(key instanceof PrivateKey);
-
-            PrivateKey expectedKey = (PrivateKey) key;
-
-            X509Certificate expectedCert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
-                    TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
-
-            assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
-                    expectedCert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-            Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-
-            assertTrue(entry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
-            assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null);
-        }
-
-        // Replace key #1 with new chain
-        {
-            Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
-
-            assertTrue(key instanceof PrivateKey);
-
-            PrivateKey expectedKey = (PrivateKey) key;
-
-            X509Certificate expectedCert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
-                    TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
-
-            mKeyStore.setKeyEntry(TEST_ALIAS_1, expectedKey, null,
-                    new Certificate[] { expectedCert });
-
-            Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-
-            assertTrue(entry instanceof PrivateKeyEntry);
-
-            PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
-            assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null);
-        }
-    }
-
-    public void testKeyStore_SetKeyEntry_ReplacedChain_DifferentPrivateKey_Encrypted_Failure()
-            throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        // Create key #1
-        {
-            final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
-            assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
-                    NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
-
-            X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
-                    TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
-
-            assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
-                    cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        }
-
-        // Create key #2
-        {
-            final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_2;
-            assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
-                    NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
-
-            X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2,
-                    TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
-
-            assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_2,
-                    cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        }
-
-        // Replace key #1 with key #2
-        {
-            Key key1 = mKeyStore.getKey(TEST_ALIAS_2, null);
-
-            X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2,
-                    TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
-
-            try {
-                mKeyStore.setKeyEntry(TEST_ALIAS_1, key1, null, new Certificate[] { cert });
-                fail("Should not allow setting of KeyEntry with wrong PrivaetKey");
-            } catch (KeyStoreException success) {
-            }
-        }
-    }
-
-    public void testKeyStore_SetKeyEntry_ReplacedChain_UnencryptedToEncrypted_Failure()
-            throws Exception {
-        mKeyStore.load(null, null);
-
-        // Create key #1
-        {
-            final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
-            assertTrue(mAndroidKeyStore.generate(privateKeyAlias,
-                    android.security.KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024,
-                    android.security.KeyStore.FLAG_NONE, null));
-
-            X509Certificate cert =
-                    generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1, TEST_DN_1,
-                            NOW, NOW_PLUS_10_YEARS);
-
-            assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
-                    cert.getEncoded(), android.security.KeyStore.UID_SELF,
-                    android.security.KeyStore.FLAG_NONE));
-        }
-
-        // Replace with one that requires encryption
-        {
-            Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-
-            try {
-                mKeyStore.setEntry(TEST_ALIAS_1, entry,
-                        new KeyStoreParameter.Builder(getContext())
-                                .setEncryptionRequired(true)
-                                .build());
-                fail("Should not allow setting of Entry without unlocked keystore");
-            } catch (KeyStoreException success) {
-            }
-
-            assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
-            assertTrue(mAndroidKeyStore.isUnlocked());
-
-            mKeyStore.setEntry(TEST_ALIAS_1, entry,
-                    new KeyStoreParameter.Builder(getContext())
-                            .setEncryptionRequired(true)
-                            .build());
-        }
-    }
-
-    public void testKeyStore_Size_Encrypted_Success() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertEquals("The keystore size should match expected", 1, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_1 });
-
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
-        assertEquals("The keystore size should match expected", 2, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
-
-        assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3,
-                KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
-                null));
-
-        assertEquals("The keystore size should match expected", 3, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
-
-        assertTrue(mAndroidKeyStore.delete(Credentials.CA_CERTIFICATE + TEST_ALIAS_1));
-
-        assertEquals("The keystore size should match expected", 2, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
-
-        assertTrue(mAndroidKeyStore.delete(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3));
-
-        assertEquals("The keystore size should match expected", 1, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_2 });
-    }
-
-    public void testKeyStore_Store_LoadStoreParam_Encrypted_Failure() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        try {
-            mKeyStore.store(null);
-            fail("Should throw UnsupportedOperationException when trying to store");
-        } catch (UnsupportedOperationException success) {
-        }
-    }
-
-    public void testKeyStore_Load_InputStreamSupplied_Encrypted_Failure() throws Exception {
-        byte[] buf = "FAKE KEYSTORE".getBytes();
-        ByteArrayInputStream is = new ByteArrayInputStream(buf);
-
-        try {
-            mKeyStore.load(is, null);
-            fail("Should throw IllegalArgumentException when InputStream is supplied");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testKeyStore_Load_PasswordSupplied_Encrypted_Failure() throws Exception {
-        try {
-            mKeyStore.load(null, "password".toCharArray());
-            fail("Should throw IllegalArgumentException when password is supplied");
-        } catch (IllegalArgumentException success) {
-        }
-    }
-
-    public void testKeyStore_Store_OutputStream_Encrypted_Failure() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        OutputStream sink = new ByteArrayOutputStream();
-        try {
-            mKeyStore.store(sink, null);
-            fail("Should throw UnsupportedOperationException when trying to store");
-        } catch (UnsupportedOperationException success) {
-        }
-
-        try {
-            mKeyStore.store(sink, "blah".toCharArray());
-            fail("Should throw UnsupportedOperationException when trying to store");
-        } catch (UnsupportedOperationException success) {
-        }
-    }
-
-    private void setupKey() throws Exception {
-        final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
-        assertTrue(mAndroidKeyStore
-                .generate(privateKeyAlias, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024,
-                        KeyStore.FLAG_ENCRYPTED, null));
-
-        X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1,
-                TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
-
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
-                cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-    }
-
-    public void testKeyStore_KeyOperations_Wrap_Encrypted_Success() throws Exception {
-        setupPassword();
-        mKeyStore.load(null, null);
-
-        setupKey();
-
-        // Test key usage
-        Entry e = mKeyStore.getEntry(TEST_ALIAS_1, null);
-        assertNotNull(e);
-        assertTrue(e instanceof PrivateKeyEntry);
-
-        PrivateKeyEntry privEntry = (PrivateKeyEntry) e;
-        PrivateKey privKey = privEntry.getPrivateKey();
-        assertNotNull(privKey);
-
-        PublicKey pubKey = privEntry.getCertificate().getPublicKey();
-
-        Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
-        c.init(Cipher.WRAP_MODE, pubKey);
-
-        byte[] expectedKey = new byte[] {
-                0x00, 0x05, (byte) 0xAA, (byte) 0x0A5, (byte) 0xFF, 0x55, 0x0A
-        };
-
-        SecretKey expectedSecret = new SecretKeySpec(expectedKey, "AES");
-
-        byte[] wrappedExpected = c.wrap(expectedSecret);
-
-        c.init(Cipher.UNWRAP_MODE, privKey);
-        SecretKey actualSecret = (SecretKey) c.unwrap(wrappedExpected, "AES", Cipher.SECRET_KEY);
-
-        assertEquals(Arrays.toString(expectedSecret.getEncoded()),
-                Arrays.toString(actualSecret.getEncoded()));
-    }
-}
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index 05fec5e..8efda2a 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -19,10 +19,12 @@
 # Build the legacy-test library
 # =============================
 # This contains the junit.framework and android.test classes that were in
-# Android API level 25.
+# Android API level 25 excluding those from android.test.runner.
+# Also contains the com.android.internal.util.Predicate[s] classes.
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
 LOCAL_MODULE := legacy-test
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
@@ -30,17 +32,35 @@
 include $(BUILD_JAVA_LIBRARY)
 
 # Build the legacy-android-test library
-# =============================
-# This contains the android.test classes that were in Android API level 25.
+# =====================================
+# This contains the android.test classes that were in Android API level 25,
+# including those from android.test.runner.
+# Also contains the com.android.internal.util.Predicate[s] classes.
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src/android)
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src/android) \
+    $(call all-java-files-under, ../test-runner/src/android) \
+    $(call all-java-files-under, src/com)
 LOCAL_MODULE := legacy-android-test
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core-oj core-libart framework junit
 
 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/core/java/com/android/internal/util/Predicate.java b/legacy-test/src/com/android/internal/util/Predicate.java
similarity index 100%
rename from core/java/com/android/internal/util/Predicate.java
rename to legacy-test/src/com/android/internal/util/Predicate.java
diff --git a/core/java/com/android/internal/util/Predicates.java b/legacy-test/src/com/android/internal/util/Predicates.java
similarity index 99%
rename from core/java/com/android/internal/util/Predicates.java
rename to legacy-test/src/com/android/internal/util/Predicates.java
index c006564..fe1ff15 100644
--- a/core/java/com/android/internal/util/Predicates.java
+++ b/legacy-test/src/com/android/internal/util/Predicates.java
@@ -21,6 +21,8 @@
 /**
  * Predicates contains static methods for creating the standard set of
  * {@code Predicate} objects.
+ *
+ * @hide
  */
 public class Predicates {
 
diff --git a/core/tests/utiltests/src/com/android/internal/util/PredicatesTest.java b/legacy-test/tests/com/android/internal/util/PredicatesTest.java
similarity index 100%
rename from core/tests/utiltests/src/com/android/internal/util/PredicatesTest.java
rename to legacy-test/tests/com/android/internal/util/PredicatesTest.java
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
new file mode 100644
index 0000000..558cdc0
--- /dev/null
+++ b/libs/hwui/Android.bp
@@ -0,0 +1,336 @@
+cc_defaults {
+    name: "hwui_defaults",
+    defaults: [
+        "hwui_static_deps",
+
+        //"hwui_bugreport_font_cache_usage",
+        //"hwui_compile_for_perf",
+        "hwui_enable_renderscript",
+
+        // Enables fine-grained GLES error checking
+        // If enabled, every GLES call is wrapped & error checked
+        // Has moderate overhead
+        //"hwui_enable_opengl-validation",
+    ],
+
+    cflags: [
+        "-DEGL_EGLEXT_PROTOTYPES",
+        "-DGL_GLEXT_PROTOTYPES",
+        "-DATRACE_TAG=ATRACE_TAG_VIEW",
+        "-DLOG_TAG=\"OpenGLRenderer\"",
+        "-Wall",
+        "-Wno-unused-parameter",
+        "-Wunreachable-code",
+        "-Werror",
+        "-fvisibility=hidden",
+        "-DHWUI_NEW_OPS",
+
+        // GCC false-positives on this warning, and since we -Werror that's
+        // a problem
+        "-Wno-free-nonheap-object",
+    ],
+
+    include_dirs: [
+        "external/skia/include/private",
+        "external/skia/src/core",
+    ],
+
+    product_variables: {
+        device_uses_hwc2: {
+            cflags: ["-DUSE_HWC2"],
+        },
+    },
+}
+
+cc_defaults {
+    name: "hwui_static_deps",
+    shared_libs: [
+        "liblog",
+        "libcutils",
+        "libutils",
+        "libEGL",
+        "libGLESv2",
+        "libskia",
+        "libui",
+        "libgui",
+        "libprotobuf-cpp-lite",
+        "libharfbuzz_ng",
+        "libft2",
+        "libminikin",
+        "libandroidfw",
+    ],
+}
+
+cc_defaults {
+    name: "hwui_bugreport_font_cache_usage",
+    srcs: ["font/FontCacheHistoryTracker.cpp"],
+    cflags: ["-DBUGREPORT_FONT_CACHE_USAGE"],
+}
+
+cc_defaults {
+    name: "hwui_compile_for_perf",
+    // TODO: Non-arm?
+    cflags: [
+        "-fno-omit-frame-pointer",
+        "-marm",
+        "-mapcs",
+    ],
+}
+
+cc_defaults {
+    name: "hwui_enable_renderscript",
+    cflags: ["-DANDROID_ENABLE_RENDERSCRIPT"],
+    include_dirs: [
+        "frameworks/rs/cpp",
+        "frameworks/rs",
+    ],
+    shared_libs: ["libRScpp"],
+}
+
+cc_defaults {
+    name: "hwui_debug",
+    cflags: ["-include debug/wrap_gles.h"],
+    srcs: [
+        "debug/wrap_gles.cpp",
+    ],
+    include_dirs: ["frameworks/native/opengl/libs/GLES2"],
+}
+
+cc_defaults {
+    name: "hwui_enable_opengl_validation",
+    defaults: ["hwui_debug"],
+    cflags: ["-DDEBUG_OPENGL=3"],
+}
+
+// ------------------------
+// library
+// ------------------------
+
+cc_defaults {
+    name: "libhwui_defaults",
+    defaults: ["hwui_defaults"],
+    srcs: [
+        "font/CacheTexture.cpp",
+        "font/Font.cpp",
+        "hwui/Canvas.cpp",
+        "hwui/MinikinSkia.cpp",
+        "hwui/MinikinUtils.cpp",
+        "hwui/PaintImpl.cpp",
+        "hwui/Typeface.cpp",
+        "renderstate/Blend.cpp",
+        "renderstate/MeshState.cpp",
+        "renderstate/OffscreenBufferPool.cpp",
+        "renderstate/PixelBufferState.cpp",
+        "renderstate/RenderState.cpp",
+        "renderstate/Scissor.cpp",
+        "renderstate/Stencil.cpp",
+        "renderstate/TextureState.cpp",
+        "renderthread/CanvasContext.cpp",
+        "renderthread/DrawFrameTask.cpp",
+        "renderthread/EglManager.cpp",
+        "renderthread/RenderProxy.cpp",
+        "renderthread/RenderTask.cpp",
+        "renderthread/RenderThread.cpp",
+        "renderthread/TimeLord.cpp",
+        "thread/TaskManager.cpp",
+        "utils/Blur.cpp",
+        "utils/GLUtils.cpp",
+        "utils/LinearAllocator.cpp",
+        "utils/NinePatchImpl.cpp",
+        "utils/StringUtils.cpp",
+        "utils/TestWindowContext.cpp",
+        "utils/VectorDrawableUtils.cpp",
+        "AmbientShadow.cpp",
+        "AnimationContext.cpp",
+        "Animator.cpp",
+        "AnimatorManager.cpp",
+        "AssetAtlas.cpp",
+        "BakedOpDispatcher.cpp",
+        "BakedOpRenderer.cpp",
+        "BakedOpState.cpp",
+        "Caches.cpp",
+        "CanvasState.cpp",
+        "ClipArea.cpp",
+        "DamageAccumulator.cpp",
+        "DeferredDisplayList.cpp",
+        "DeferredLayerUpdater.cpp",
+        "DeviceInfo.cpp",
+        "DisplayList.cpp",
+        "DisplayListCanvas.cpp",
+        "Dither.cpp",
+        "Extensions.cpp",
+        "FboCache.cpp",
+        "FontRenderer.cpp",
+        "FrameBuilder.cpp",
+        "FrameInfo.cpp",
+        "FrameInfoVisualizer.cpp",
+        "GammaFontRenderer.cpp",
+        "GlopBuilder.cpp",
+        "GpuMemoryTracker.cpp",
+        "GradientCache.cpp",
+        "Image.cpp",
+        "Interpolator.cpp",
+        "JankTracker.cpp",
+        "Layer.cpp",
+        "LayerBuilder.cpp",
+        "LayerCache.cpp",
+        "LayerRenderer.cpp",
+        "LayerUpdateQueue.cpp",
+        "Matrix.cpp",
+        "OpDumper.cpp",
+        "OpenGLRenderer.cpp",
+        "Patch.cpp",
+        "PatchCache.cpp",
+        "PathCache.cpp",
+        "PathTessellator.cpp",
+        "PathParser.cpp",
+        "PixelBuffer.cpp",
+        "Program.cpp",
+        "ProgramCache.cpp",
+        "Properties.cpp",
+        "PropertyValuesHolder.cpp",
+        "PropertyValuesAnimatorSet.cpp",
+        "Readback.cpp",
+        "RecordingCanvas.cpp",
+        "RenderBufferCache.cpp",
+        "RenderNode.cpp",
+        "RenderProperties.cpp",
+        "ResourceCache.cpp",
+        "ShadowTessellator.cpp",
+        "SkiaCanvas.cpp",
+        "SkiaCanvasProxy.cpp",
+        "SkiaShader.cpp",
+        "Snapshot.cpp",
+        "SpotShadow.cpp",
+        "TessellationCache.cpp",
+        "TextDropShadowCache.cpp",
+        "Texture.cpp",
+        "TextureCache.cpp",
+        "VectorDrawable.cpp",
+        "protos/hwui.proto",
+    ],
+
+    proto: {
+        export_proto_headers: true,
+    },
+
+    export_include_dirs: ["."],
+}
+
+cc_library {
+    name: "libhwui",
+    defaults: ["libhwui_defaults"],
+}
+
+// ------------------------
+// static library null gpu
+// ------------------------
+
+cc_library_static {
+    name: "libhwui_static_null_gpu",
+    defaults: ["libhwui_defaults"],
+    cflags: ["-DHWUI_NULL_GPU"],
+    srcs: [
+        "debug/nullegl.cpp",
+        "debug/nullgles.cpp",
+    ],
+    export_include_dirs: ["."],
+}
+
+cc_defaults {
+    name: "hwui_test_defaults",
+    defaults: ["hwui_defaults"],
+    srcs: [
+        "tests/common/scenes/*.cpp",
+        "tests/common/TestContext.cpp",
+        "tests/common/TestScene.cpp",
+        "tests/common/TestUtils.cpp",
+    ],
+}
+
+// ------------------------
+// unit tests
+// ------------------------
+
+cc_test {
+    name: "hwui_unit_tests",
+    defaults: ["hwui_test_defaults"],
+    static_libs: ["libhwui_static_null_gpu"],
+    shared_libs: ["libmemunreachable"],
+    cflags: ["-DHWUI_NULL_GPU"],
+
+    srcs: [
+        "tests/unit/main.cpp",
+        "tests/unit/BakedOpDispatcherTests.cpp",
+        "tests/unit/BakedOpRendererTests.cpp",
+        "tests/unit/BakedOpStateTests.cpp",
+        "tests/unit/CanvasStateTests.cpp",
+        "tests/unit/ClipAreaTests.cpp",
+        "tests/unit/DamageAccumulatorTests.cpp",
+        "tests/unit/DeviceInfoTests.cpp",
+        "tests/unit/FatVectorTests.cpp",
+        "tests/unit/FontRendererTests.cpp",
+        "tests/unit/FrameBuilderTests.cpp",
+        "tests/unit/GlopBuilderTests.cpp",
+        "tests/unit/GpuMemoryTrackerTests.cpp",
+        "tests/unit/GradientCacheTests.cpp",
+        "tests/unit/LayerUpdateQueueTests.cpp",
+        "tests/unit/LeakCheckTests.cpp",
+        "tests/unit/LinearAllocatorTests.cpp",
+        "tests/unit/MatrixTests.cpp",
+        "tests/unit/OffscreenBufferPoolTests.cpp",
+        "tests/unit/OpDumperTests.cpp",
+        "tests/unit/RecordingCanvasTests.cpp",
+        "tests/unit/RenderNodeTests.cpp",
+        "tests/unit/RenderPropertiesTests.cpp",
+        "tests/unit/SkiaBehaviorTests.cpp",
+        "tests/unit/SkiaCanvasTests.cpp",
+        "tests/unit/SnapshotTests.cpp",
+        "tests/unit/StringUtilsTests.cpp",
+        "tests/unit/TestUtilsTests.cpp",
+        "tests/unit/TextDropShadowCacheTests.cpp",
+        "tests/unit/VectorDrawableTests.cpp",
+    ],
+}
+
+// ------------------------
+// Macro-bench app
+// ------------------------
+
+cc_test {
+    name: "hwuitest",
+    defaults: ["hwui_test_defaults"],
+    gtest: false,
+
+    // set to libhwui_static_null_gpu to skip actual GL commands
+    whole_static_libs: ["libhwui"],
+
+    srcs: [
+        "tests/macrobench/TestSceneRunner.cpp",
+        "tests/macrobench/main.cpp",
+    ],
+}
+
+// ------------------------
+// Micro-bench app
+// ---------------------
+
+cc_benchmark {
+    name: "hwuimicro",
+    defaults: ["hwui_test_defaults"],
+
+    cflags: ["-DHWUI_NULL_GPU"],
+
+    whole_static_libs: ["libhwui_static_null_gpu"],
+
+    srcs: [
+        "tests/microbench/main.cpp",
+        "tests/microbench/DisplayListCanvasBench.cpp",
+        "tests/microbench/FontBench.cpp",
+        "tests/microbench/FrameBuilderBench.cpp",
+        "tests/microbench/LinearAllocatorBench.cpp",
+        "tests/microbench/PathParserBench.cpp",
+        "tests/microbench/ShadowBench.cpp",
+        "tests/microbench/TaskManagerBench.cpp",
+    ],
+}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
deleted file mode 100644
index bf9423c..0000000
--- a/libs/hwui/Android.mk
+++ /dev/null
@@ -1,357 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-HWUI_NEW_OPS := true
-BUGREPORT_FONT_CACHE_USAGE := false
-
-# Enables fine-grained GLES error checking
-# If set to true, every GLES call is wrapped & error checked
-# Has moderate overhead
-HWUI_ENABLE_OPENGL_VALIDATION := false
-
-hwui_src_files := \
-    font/CacheTexture.cpp \
-    font/Font.cpp \
-    hwui/Canvas.cpp \
-    hwui/MinikinSkia.cpp \
-    hwui/MinikinUtils.cpp \
-    hwui/PaintImpl.cpp \
-    hwui/Typeface.cpp \
-    renderstate/Blend.cpp \
-    renderstate/MeshState.cpp \
-    renderstate/OffscreenBufferPool.cpp \
-    renderstate/PixelBufferState.cpp \
-    renderstate/RenderState.cpp \
-    renderstate/Scissor.cpp \
-    renderstate/Stencil.cpp \
-    renderstate/TextureState.cpp \
-    renderthread/CanvasContext.cpp \
-    renderthread/DrawFrameTask.cpp \
-    renderthread/EglManager.cpp \
-    renderthread/RenderProxy.cpp \
-    renderthread/RenderTask.cpp \
-    renderthread/RenderThread.cpp \
-    renderthread/TimeLord.cpp \
-    thread/TaskManager.cpp \
-    utils/Blur.cpp \
-    utils/GLUtils.cpp \
-    utils/LinearAllocator.cpp \
-    utils/NinePatchImpl.cpp \
-    utils/StringUtils.cpp \
-    utils/TestWindowContext.cpp \
-    utils/VectorDrawableUtils.cpp \
-    AmbientShadow.cpp \
-    AnimationContext.cpp \
-    Animator.cpp \
-    AnimatorManager.cpp \
-    AssetAtlas.cpp \
-    Caches.cpp \
-    CanvasState.cpp \
-    ClipArea.cpp \
-    DamageAccumulator.cpp \
-    DeferredDisplayList.cpp \
-    DeferredLayerUpdater.cpp \
-    DeviceInfo.cpp \
-    DisplayList.cpp \
-    DisplayListCanvas.cpp \
-    Dither.cpp \
-    Extensions.cpp \
-    FboCache.cpp \
-    FontRenderer.cpp \
-    FrameInfo.cpp \
-    FrameInfoVisualizer.cpp \
-    GammaFontRenderer.cpp \
-    GlopBuilder.cpp \
-    GpuMemoryTracker.cpp \
-    GradientCache.cpp \
-    Image.cpp \
-    Interpolator.cpp \
-    JankTracker.cpp \
-    Layer.cpp \
-    LayerCache.cpp \
-    LayerRenderer.cpp \
-    LayerUpdateQueue.cpp \
-    Matrix.cpp \
-    OpenGLRenderer.cpp \
-    Patch.cpp \
-    PatchCache.cpp \
-    PathCache.cpp \
-    PathTessellator.cpp \
-    PathParser.cpp \
-    PixelBuffer.cpp \
-    Program.cpp \
-    ProgramCache.cpp \
-    Properties.cpp \
-    PropertyValuesHolder.cpp \
-    PropertyValuesAnimatorSet.cpp \
-    Readback.cpp \
-    RenderBufferCache.cpp \
-    RenderNode.cpp \
-    RenderProperties.cpp \
-    ResourceCache.cpp \
-    ShadowTessellator.cpp \
-    SkiaCanvas.cpp \
-    SkiaCanvasProxy.cpp \
-    SkiaShader.cpp \
-    Snapshot.cpp \
-    SpotShadow.cpp \
-    TessellationCache.cpp \
-    TextDropShadowCache.cpp \
-    Texture.cpp \
-    TextureCache.cpp \
-    VectorDrawable.cpp \
-    protos/hwui.proto
-
-hwui_test_common_src_files := \
-    $(call all-cpp-files-under, tests/common/scenes) \
-    tests/common/TestContext.cpp \
-    tests/common/TestScene.cpp \
-    tests/common/TestUtils.cpp
-
-hwui_cflags := \
-    -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES \
-    -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" \
-    -Wall -Wno-unused-parameter -Wunreachable-code -Werror
-
-ifeq ($(TARGET_USES_HWC2),true)
-    hwui_cflags += -DUSE_HWC2
-endif
-
-# GCC false-positives on this warning, and since we -Werror that's
-# a problem
-hwui_cflags += -Wno-free-nonheap-object
-
-ifeq (true, $(HWUI_NEW_OPS))
-    hwui_src_files += \
-        BakedOpDispatcher.cpp \
-        BakedOpRenderer.cpp \
-        BakedOpState.cpp \
-        FrameBuilder.cpp \
-        LayerBuilder.cpp \
-        OpDumper.cpp \
-        RecordingCanvas.cpp
-
-    hwui_cflags += -DHWUI_NEW_OPS
-
-endif
-
-ifeq (true, $(BUGREPORT_FONT_CACHE_USAGE))
-    hwui_src_files += \
-        font/FontCacheHistoryTracker.cpp
-    hwui_cflags += -DBUGREPORT_FONT_CACHE_USAGE
-endif
-
-
-ifndef HWUI_COMPILE_SYMBOLS
-    hwui_cflags += -fvisibility=hidden
-endif
-
-ifdef HWUI_COMPILE_FOR_PERF
-    # TODO: Non-arm?
-    hwui_cflags += -fno-omit-frame-pointer -marm -mapcs
-endif
-
-# This has to be lazy-resolved because it depends on the LOCAL_MODULE_CLASS
-# which varies depending on what is being built
-define hwui_proto_include
-$(call local-generated-sources-dir)/proto/$(LOCAL_PATH)
-endef
-
-hwui_c_includes += \
-    external/skia/include/private \
-    external/skia/src/core \
-    external/harfbuzz_ng/src \
-    external/freetype/include
-
-ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
-    hwui_cflags += -DANDROID_ENABLE_RENDERSCRIPT
-    hwui_c_includes += \
-        $(call intermediates-dir-for,STATIC_LIBRARIES,TARGET,) \
-        frameworks/rs/cpp \
-        frameworks/rs
-endif
-
-ifeq (true, $(HWUI_ENABLE_OPENGL_VALIDATION))
-    hwui_cflags += -include debug/wrap_gles.h
-    hwui_src_files += debug/wrap_gles.cpp
-    hwui_c_includes += frameworks/native/opengl/libs/GLES2
-    hwui_cflags += -DDEBUG_OPENGL=3
-endif
-
-
-# ------------------------
-# static library
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := STATIC_LIBRARIES
-LOCAL_MODULE := libhwui_static
-LOCAL_CFLAGS := $(hwui_cflags)
-LOCAL_SRC_FILES := $(hwui_src_files)
-LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
-        $(LOCAL_PATH) \
-        $(call hwui_proto_include)
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_STATIC_LIBRARY)
-
-# ------------------------
-# static library null gpu
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := STATIC_LIBRARIES
-LOCAL_MODULE := libhwui_static_null_gpu
-LOCAL_CFLAGS := \
-        $(hwui_cflags) \
-        -DHWUI_NULL_GPU
-LOCAL_SRC_FILES := \
-        $(hwui_src_files) \
-        debug/nullegl.cpp \
-        debug/nullgles.cpp
-LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
-        $(LOCAL_PATH) \
-        $(call hwui_proto_include)
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_STATIC_LIBRARY)
-
-# ------------------------
-# shared library
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-LOCAL_MODULE := libhwui
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_SHARED_LIBRARY)
-
-# ------------------------
-# unit tests
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := hwui_unit_tests
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
-LOCAL_SHARED_LIBRARIES := libmemunreachable
-LOCAL_CFLAGS := \
-        $(hwui_cflags) \
-        -DHWUI_NULL_GPU
-LOCAL_C_INCLUDES := $(hwui_c_includes)
-
-LOCAL_SRC_FILES += \
-    $(hwui_test_common_src_files) \
-    tests/unit/main.cpp \
-    tests/unit/CanvasStateTests.cpp \
-    tests/unit/ClipAreaTests.cpp \
-    tests/unit/DamageAccumulatorTests.cpp \
-    tests/unit/DeviceInfoTests.cpp \
-    tests/unit/FatVectorTests.cpp \
-    tests/unit/FontRendererTests.cpp \
-    tests/unit/GlopBuilderTests.cpp \
-    tests/unit/GpuMemoryTrackerTests.cpp \
-    tests/unit/GradientCacheTests.cpp \
-    tests/unit/LayerUpdateQueueTests.cpp \
-    tests/unit/LinearAllocatorTests.cpp \
-    tests/unit/MatrixTests.cpp \
-    tests/unit/OffscreenBufferPoolTests.cpp \
-    tests/unit/RenderNodeTests.cpp \
-    tests/unit/RenderPropertiesTests.cpp \
-    tests/unit/SkiaBehaviorTests.cpp \
-    tests/unit/SnapshotTests.cpp \
-    tests/unit/StringUtilsTests.cpp \
-    tests/unit/TestUtilsTests.cpp \
-    tests/unit/TextDropShadowCacheTests.cpp \
-    tests/unit/VectorDrawableTests.cpp
-
-ifeq (true, $(HWUI_NEW_OPS))
-    LOCAL_SRC_FILES += \
-        tests/unit/BakedOpDispatcherTests.cpp \
-        tests/unit/BakedOpRendererTests.cpp \
-        tests/unit/BakedOpStateTests.cpp \
-        tests/unit/FrameBuilderTests.cpp \
-        tests/unit/LeakCheckTests.cpp \
-        tests/unit/OpDumperTests.cpp \
-        tests/unit/RecordingCanvasTests.cpp \
-        tests/unit/SkiaCanvasTests.cpp
-endif
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_NATIVE_TEST)
-
-# ------------------------
-# Macro-bench app
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp
-LOCAL_MODULE:= hwuitest
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := hwuitest
-LOCAL_MODULE_STEM_64 := hwuitest64
-LOCAL_CFLAGS := $(hwui_cflags)
-LOCAL_C_INCLUDES := $(hwui_c_includes)
-
-# set to libhwui_static_null_gpu to skip actual GL commands
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
-
-LOCAL_SRC_FILES += \
-    $(hwui_test_common_src_files) \
-    tests/macrobench/TestSceneRunner.cpp \
-    tests/macrobench/main.cpp
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_EXECUTABLE)
-
-# ------------------------
-# Micro-bench app
-# ---------------------
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp
-LOCAL_MODULE:= hwuimicro
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := hwuimicro
-LOCAL_MODULE_STEM_64 := hwuimicro64
-LOCAL_CFLAGS := \
-        $(hwui_cflags) \
-        -DHWUI_NULL_GPU
-
-LOCAL_C_INCLUDES := $(hwui_c_includes)
-
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu
-LOCAL_STATIC_LIBRARIES := libgoogle-benchmark
-
-LOCAL_SRC_FILES += \
-    $(hwui_test_common_src_files) \
-    tests/microbench/main.cpp \
-    tests/microbench/DisplayListCanvasBench.cpp \
-    tests/microbench/FontBench.cpp \
-    tests/microbench/LinearAllocatorBench.cpp \
-    tests/microbench/PathParserBench.cpp \
-    tests/microbench/ShadowBench.cpp \
-    tests/microbench/TaskManagerBench.cpp
-
-ifeq (true, $(HWUI_NEW_OPS))
-    LOCAL_SRC_FILES += \
-        tests/microbench/FrameBuilderBench.cpp
-endif
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_EXECUTABLE)
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index 6433c86..b95fd63 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -112,6 +112,7 @@
         } vertices;
 
         int elementCount;
+        int vertexCount; // only used for meshes (for glDrawRangeElements)
         TextureVertex mappedVertices[4];
     } mesh;
 
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index e502725..8fcd1ea 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -210,6 +210,7 @@
             alphaVertex ? kAlphaVertexStride : kVertexStride };
     mOutGlop->mesh.elementCount = indices
                 ? vertexBuffer.getIndexCount() : vertexBuffer.getVertexCount();
+    mOutGlop->mesh.vertexCount = vertexBuffer.getVertexCount(); // used for glDrawRangeElements()
     return *this;
 }
 
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 2132c2b..9c253c4 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -265,7 +265,6 @@
                 / kSlowFrameBucketIntervalMs;
         framebucket = std::min(framebucket,
                 static_cast<uint32_t>(mData->slowFrameCounts.size() - 1));
-        framebucket = std::max(framebucket, 0u);
         mData->slowFrameCounts[framebucket]++;
     }
 
diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk
deleted file mode 100644
index 8dae273..0000000
--- a/libs/hwui/hwui_static_deps.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-###############################################################################
-#
-#
-# This file contains the shared and static dependencies needed by any target
-# that attempts to statically link HWUI (i.e. libhwui_static build target). This
-# file should be included by any target that lists libhwui_static as a
-# dependency.
-#
-# This is a workaround for the fact that the build system does not add these
-# transitive dependencies when it attempts to link libhwui_static into another
-# library.
-#
-###############################################################################
-
-LOCAL_SHARED_LIBRARIES += \
-    liblog \
-    libcutils \
-    libutils \
-    libEGL \
-    libGLESv2 \
-    libskia \
-    libui \
-    libgui \
-    libprotobuf-cpp-lite \
-    libharfbuzz_ng \
-    libft2 \
-    libminikin \
-    libandroidfw
-
-ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
-    LOCAL_SHARED_LIBRARIES += libRScpp
-endif
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 5e60064..c4e8e4f 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -364,18 +364,28 @@
         const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position);
         while (elementsCount > 0) {
             GLsizei drawCount = std::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
+            GLsizei vertexCount = (drawCount / 6) * 4;
             meshState().bindPositionVertexPointer(vertexData, vertices.stride);
             if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
                 meshState().bindTexCoordsVertexPointer(
                         vertexData + kMeshTextureOffset, vertices.stride);
             }
 
-            glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr);
+            if (mCaches->extensions().getMajorGlVersion() >= 3) {
+                glDrawRangeElements(mesh.primitiveMode, 0, vertexCount-1, drawCount, GL_UNSIGNED_SHORT, nullptr);
+            } else {
+                glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr);
+            }
             elementsCount -= drawCount;
-            vertexData += (drawCount / 6) * 4 * vertices.stride;
+            vertexData += vertexCount * vertices.stride;
         }
     } else if (indices.bufferObject || indices.indices) {
-        glDrawElements(mesh.primitiveMode, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
+        if (mCaches->extensions().getMajorGlVersion() >= 3) {
+            // use glDrawRangeElements to reduce CPU overhead (otherwise the driver has to determine the min/max index values)
+            glDrawRangeElements(mesh.primitiveMode, 0, mesh.vertexCount-1, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
+        } else {
+            glDrawElements(mesh.primitiveMode, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
+        }
     } else {
         glDrawArrays(mesh.primitiveMode, 0, mesh.elementCount);
     }
diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
index 6b7b721..adf4d46 100644
--- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
@@ -119,7 +119,7 @@
     layerPaint.setAlpha(128);
     OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case
     LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer);
-    testUnmergedGlopDispatch(renderThread, &op, [&renderThread] (const Glop& glop) {
+    testUnmergedGlopDispatch(renderThread, &op, [] (const Glop& glop) {
         ADD_FAILURE() << "Nothing should happen";
     }, 0);
 }
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
new file mode 100644
index 0000000..84c6cf4
--- /dev/null
+++ b/libs/input/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2010 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.
+
+cc_library_shared {
+    name: "libinputservice",
+
+    srcs: [
+        "PointerController.cpp",
+        "SpriteController.cpp",
+    ],
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libutils",
+        "libskia",
+        "libgui",
+        "libui",
+        "libinput",
+        "libinputflinger",
+    ],
+
+    include_dirs: ["frameworks/native/services"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+
+}
diff --git a/libs/input/Android.mk b/libs/input/Android.mk
deleted file mode 100644
index 2bbfdcc..0000000
--- a/libs/input/Android.mk
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 2010 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
-    PointerController.cpp \
-    SpriteController.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-    libcutils \
-    liblog \
-    libutils \
-    libskia \
-    libgui \
-    libui \
-    libinput \
-    libinputflinger
-
-LOCAL_C_INCLUDES := \
-    frameworks/native/services
-
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-LOCAL_MODULE:= libinputservice
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_SHARED_LIBRARY)
-
-
-# Include subdirectory makefiles
-# ============================================================
-
-# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
-# team really wants is to build the stuff defined by this makefile.
-ifeq (,$(ONE_SHOT_MAKEFILE))
-include $(call first-makefiles-under,$(LOCAL_PATH))
-endif
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index 93e86af..5b03907 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -245,8 +245,7 @@
     }
 
     public boolean getInEmergency() {
-        boolean isInEmergencyCallback = Boolean.parseBoolean(
-                SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE));
+        boolean isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
         return mIsInEmergency || isInEmergencyCallback;
     }
 
diff --git a/media/java/Android.bp b/media/java/Android.bp
new file mode 100644
index 0000000..0810699
--- /dev/null
+++ b/media/java/Android.bp
@@ -0,0 +1,4 @@
+filegroup {
+    name: "IMidiDeviceServer.aidl",
+    srcs: ["android/media/midi/IMidiDeviceServer.aidl"],
+}
diff --git a/media/java/android/media/session/ICallback.aidl b/media/java/android/media/session/ICallback.aidl
new file mode 100644
index 0000000..bb8a3cc
--- /dev/null
+++ b/media/java/android/media/session/ICallback.aidl
@@ -0,0 +1,34 @@
+/* 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.media.session;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.media.session.MediaSession;
+import android.view.KeyEvent;
+
+/**
+ * @hide
+ */
+oneway interface ICallback {
+    void onMediaKeyEventDispatchedToMediaSession(in KeyEvent event,
+            in MediaSession.Token sessionToken);
+    void onMediaKeyEventDispatchedToMediaButtonReceiver(in KeyEvent event,
+            in ComponentName mediaButtonReceiver);
+
+    void onAddressedPlayerChangedToMediaSession(in MediaSession.Token sessionToken);
+    void onAddressedPlayerChangedToMediaButtonReceiver(in ComponentName mediaButtonReceiver);
+}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index bb59e5b..575e7d7 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -18,6 +18,7 @@
 import android.content.ComponentName;
 import android.media.IRemoteVolumeController;
 import android.media.session.IActiveSessionsListener;
+import android.media.session.ICallback;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.os.Bundle;
@@ -41,4 +42,6 @@
 
     // For PhoneWindowManager to precheck media keys
     boolean isGlobalPriorityActive();
-}
\ No newline at end of file
+
+    void setCallback(in ICallback callback);
+}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 2364a13..31e60da 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -57,6 +57,8 @@
 
     private Context mContext;
 
+    private CallbackImpl mCallback;
+
     /**
      * @hide
      */
@@ -313,6 +315,36 @@
     }
 
     /**
+     * Set a {@link Callback}.
+     *
+     * <p>System can only have a single callback, and the callback can only be set by
+     * Bluetooth service process.
+     *
+     * @param callback A {@link Callback}. {@code null} to reset.
+     * @param handler The handler on which the callback should be invoked, or {@code null}
+     *            if the callback should be invoked on the calling thread's looper.
+     * @hide
+     */
+    public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
+        synchronized (mLock) {
+            try {
+                if (callback == null) {
+                    mCallback = null;
+                    mService.setCallback(null);
+                } else {
+                    if (handler == null) {
+                        handler = new Handler();
+                    }
+                    mCallback = new CallbackImpl(callback, handler);
+                    mService.setCallback(mCallback);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to set media key callback", e);
+            }
+        }
+    }
+
+    /**
      * Listens for changes to the list of active sessions. This can be added
      * using {@link #addOnActiveSessionsChangedListener}.
      */
@@ -320,6 +352,56 @@
         public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
     }
 
+    /**
+     * Callbacks for the media session service.
+     *
+     * <p>Called when a media key event is dispatched or the addressed player is changed.
+     * The addressed player is either the media session or the media button receiver that will
+     * receive media key events.
+     * @hide
+     */
+    public static abstract class Callback {
+        /**
+         * Called when a media key event is dispatched to the media session
+         * through the media session service.
+         *
+         * @param event Dispatched media key event.
+         * @param sessionToken The media session's token.
+         */
+        public abstract void onMediaKeyEventDispatched(KeyEvent event,
+                MediaSession.Token sessionToken);
+
+        /**
+         * Called when a media key event is dispatched to the media button receiver
+         * through the media session service.
+         * <p>MediaSessionService may broadcast key events to the media button receiver
+         * when reviving playback after the media session is released.
+         *
+         * @param event Dispatched media key event.
+         * @param mediaButtonReceiver The media button receiver.
+         */
+        public abstract void onMediaKeyEventDispatched(KeyEvent event,
+                ComponentName mediaButtonReceiver);
+
+        /**
+         * Called when the addressed player is changed to a media session.
+         * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
+         * {@link #setCallback} if the addressed player exists.
+         *
+         * @param sessionToken The media session's token.
+         */
+        public abstract void onAddressedPlayerChanged(MediaSession.Token sessionToken);
+
+        /**
+         * Called when the addressed player is changed to the media button receiver.
+         * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
+         * {@link #setCallback} if the addressed player exists.
+         *
+         * @param mediaButtonReceiver The media button receiver.
+         */
+        public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver);
+    }
+
     private static final class SessionsChangedWrapper {
         private Context mContext;
         private OnActiveSessionsChangedListener mListener;
@@ -360,4 +442,57 @@
             mHandler = null;
         }
     }
+
+    private static final class CallbackImpl extends ICallback.Stub {
+        private final Callback mCallback;
+        private final Handler mHandler;
+
+        public CallbackImpl(@NonNull Callback callback, @NonNull Handler handler) {
+            mCallback = callback;
+            mHandler = handler;
+        }
+
+        @Override
+        public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
+                MediaSession.Token sessionToken) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onMediaKeyEventDispatched(event, sessionToken);
+                }
+            });
+        }
+
+        @Override
+        public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
+                ComponentName mediaButtonReceiver) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onMediaKeyEventDispatched(event, mediaButtonReceiver);
+                }
+            });
+        }
+
+        @Override
+        public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAddressedPlayerChanged(sessionToken);
+                }
+            });
+        }
+
+        @Override
+        public void onAddressedPlayerChangedToMediaButtonReceiver(
+                ComponentName mediaButtonReceiver) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAddressedPlayerChanged(mediaButtonReceiver);
+                }
+            });
+        }
+    }
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
new file mode 100644
index 0000000..d5e2101
--- /dev/null
+++ b/media/jni/Android.bp
@@ -0,0 +1,78 @@
+cc_library_shared {
+    name: "libmedia_jni",
+
+    srcs: [
+        "android_media_ExifInterface.cpp",
+        "android_media_ImageWriter.cpp",
+        "android_media_ImageReader.cpp",
+        "android_media_MediaCrypto.cpp",
+        "android_media_MediaCodec.cpp",
+        "android_media_MediaCodecList.cpp",
+        "android_media_MediaDataSource.cpp",
+        "android_media_MediaDrm.cpp",
+        "android_media_MediaExtractor.cpp",
+        "android_media_MediaHTTPConnection.cpp",
+        "android_media_MediaMetadataRetriever.cpp",
+        "android_media_MediaMuxer.cpp",
+        "android_media_MediaPlayer.cpp",
+        "android_media_MediaProfiles.cpp",
+        "android_media_MediaRecorder.cpp",
+        "android_media_MediaScanner.cpp",
+        "android_media_MediaSync.cpp",
+        "android_media_ResampleInputStream.cpp",
+        "android_media_SyncParams.cpp",
+        "android_media_Utils.cpp",
+        "android_mtp_MtpDatabase.cpp",
+        "android_mtp_MtpDevice.cpp",
+        "android_mtp_MtpServer.cpp",
+    ],
+
+    shared_libs: [
+        "libandroid_runtime",
+        "libnativehelper",
+        "libutils",
+        "libbinder",
+        "libmedia",
+        "libmediadrm",
+        "libskia",
+        "libui",
+        "liblog",
+        "libcutils",
+        "libgui",
+        "libstagefright",
+        "libstagefright_foundation",
+        "libcamera_client",
+        "libmtp",
+        "libusbhost",
+        "libexif",
+        "libpiex",
+        "libandroidfw",
+    ],
+
+    header_libs: ["libhardware_headers"],
+
+    include_dirs: [
+        "frameworks/base/core/jni",
+        "frameworks/native/include/media/openmax",
+        "system/media/camera/include",
+    ],
+
+    export_include_dirs: ["."],
+
+    export_shared_lib_headers: [
+        "libpiex",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-error=deprecated-declarations",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
+
+subdirs = [
+    "audioeffect",
+    "soundpool",
+]
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
deleted file mode 100644
index 8640565..0000000
--- a/media/jni/Android.mk
+++ /dev/null
@@ -1,74 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
-    android_media_ExifInterface.cpp \
-    android_media_ImageWriter.cpp \
-    android_media_ImageReader.cpp \
-    android_media_MediaCrypto.cpp \
-    android_media_MediaCodec.cpp \
-    android_media_MediaCodecList.cpp \
-    android_media_MediaDataSource.cpp \
-    android_media_MediaDrm.cpp \
-    android_media_MediaExtractor.cpp \
-    android_media_MediaHTTPConnection.cpp \
-    android_media_MediaMetadataRetriever.cpp \
-    android_media_MediaMuxer.cpp \
-    android_media_MediaPlayer.cpp \
-    android_media_MediaProfiles.cpp \
-    android_media_MediaRecorder.cpp \
-    android_media_MediaScanner.cpp \
-    android_media_MediaSync.cpp \
-    android_media_ResampleInputStream.cpp \
-    android_media_SyncParams.cpp \
-    android_media_Utils.cpp \
-    android_mtp_MtpDatabase.cpp \
-    android_mtp_MtpDevice.cpp \
-    android_mtp_MtpServer.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
-    libandroid_runtime \
-    libnativehelper \
-    libutils \
-    libbinder \
-    libmedia \
-    libmediadrm \
-    libskia \
-    libui \
-    liblog \
-    libcutils \
-    libgui \
-    libstagefright \
-    libstagefright_foundation \
-    libcamera_client \
-    libmtp \
-    libusbhost \
-    libexif \
-    libpiex \
-    libandroidfw
-
-LOCAL_STATIC_LIBRARIES := \
-
-LOCAL_C_INCLUDES += \
-    external/libexif/ \
-    external/piex/ \
-    external/tremor/Tremor \
-    frameworks/base/core/jni \
-    frameworks/base/libs/hwui \
-    frameworks/av/media/libmedia \
-    frameworks/av/media/libstagefright \
-    frameworks/av/media/mtp \
-    frameworks/native/include/media/openmax \
-    $(call include-path-for, libhardware)/hardware \
-    $(PV_INCLUDES) \
-    $(JNI_H_INCLUDE)
-
-LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code
-
-LOCAL_MODULE:= libmedia_jni
-
-include $(BUILD_SHARED_LIBRARY)
-
-# build libsoundpool.so
-# build libaudioeffect_jni.so
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 724fc02..c655b7c 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -26,7 +26,6 @@
 
 #include <gui/BufferItemConsumer.h>
 #include <gui/Surface.h>
-#include <camera3.h>
 
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_view_Surface.h>
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index d5d9fc9..56df32f 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -25,7 +25,6 @@
 #include <gui/Surface.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_view_Surface.h>
-#include <camera3.h>
 #include <jni.h>
 #include <JNIHelp.h>
 
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index c62d930..458d847 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -17,6 +17,7 @@
 // #define LOG_NDEBUG 0
 #define LOG_TAG "AndroidMediaUtils"
 
+#include <hardware/camera3.h>
 #include <utils/Log.h>
 #include "android_media_Utils.h"
 
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index 39c1554..af2f2d7 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -21,7 +21,6 @@
 #include "src/piex.h"
 
 #include <android_runtime/AndroidRuntime.h>
-#include <camera3.h>
 #include <gui/CpuConsumer.h>
 #include <jni.h>
 #include <JNIHelp.h>
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
new file mode 100644
index 0000000..8ac139d
--- /dev/null
+++ b/media/jni/audioeffect/Android.bp
@@ -0,0 +1,29 @@
+cc_library_shared {
+    name: "libaudioeffect_jni",
+
+    srcs: [
+        "android_media_AudioEffect.cpp",
+        "android_media_Visualizer.cpp",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libcutils",
+        "libutils",
+        "libandroid_runtime",
+        "libnativehelper",
+        "libmedia",
+        "libaudioclient",
+    ],
+
+    header_libs: [
+        "libaudioeffects",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
diff --git a/media/jni/audioeffect/Android.mk b/media/jni/audioeffect/Android.mk
deleted file mode 100644
index 8bd8857..0000000
--- a/media/jni/audioeffect/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
-    android_media_AudioEffect.cpp \
-    android_media_Visualizer.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-    liblog \
-    libcutils \
-    libutils \
-    libandroid_runtime \
-    libnativehelper \
-    libmedia \
-    libaudioclient \
-
-LOCAL_C_INCLUDES := \
-    $(call include-path-for, audio-effects)
-
-LOCAL_MODULE:= libaudioeffect_jni
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp
new file mode 100644
index 0000000..35b7b01
--- /dev/null
+++ b/media/jni/soundpool/Android.bp
@@ -0,0 +1,28 @@
+cc_library_shared {
+    name: "libsoundpool",
+
+    srcs: [
+        "android_media_SoundPool.cpp",
+        "SoundPool.cpp",
+        "SoundPoolThread.cpp",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libcutils",
+        "libutils",
+        "libandroid_runtime",
+        "libnativehelper",
+        "libaudioclient",
+        "libmediandk",
+        "libbinder",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-error=deprecated-declarations",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
diff --git a/media/jni/soundpool/Android.mk b/media/jni/soundpool/Android.mk
deleted file mode 100644
index 509a59b..0000000
--- a/media/jni/soundpool/Android.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
-    android_media_SoundPool.cpp \
-    SoundPool.cpp \
-    SoundPoolThread.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-    liblog \
-    libcutils \
-    libutils \
-    libandroid_runtime \
-    libnativehelper \
-    libaudioclient \
-    libmediandk \
-    libbinder
-
-LOCAL_MODULE:= libsoundpool
-
-LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index d2dc440..3f45497 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -30,9 +30,9 @@
 #include "SoundPool.h"
 #include "SoundPoolThread.h"
 #include <media/AudioPolicyHelper.h>
-#include <ndk/NdkMediaCodec.h>
-#include <ndk/NdkMediaExtractor.h>
-#include <ndk/NdkMediaFormat.h>
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/NdkMediaFormat.h>
 
 namespace android
 {
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 5843637..3f9cc21 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -14,8 +14,66 @@
 
 // The headers module is in frameworks/native/Android.bp.
 ndk_library {
-    name: "libandroid.ndk",
+    name: "libandroid",
     symbol_file: "libandroid.map.txt",
     first_version: "9",
     unversioned_until: "current",
 }
+
+cc_defaults {
+    name: "libandroid_defaults",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
+
+cc_library_shared {
+    name: "libandroid",
+    defaults: ["libandroid_defaults"],
+
+    srcs: [
+        "asset_manager.cpp",
+        "choreographer.cpp",
+        "configuration.cpp",
+        "input.cpp",
+        "looper.cpp",
+        "native_activity.cpp",
+        "native_window.cpp",
+        "net.c",
+        "obb.cpp",
+        "sensor.cpp",
+        "storage_manager.cpp",
+        "trace.cpp",
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libcutils",
+        "libandroidfw",
+        "libinput",
+        "libutils",
+        "libbinder",
+        "libui",
+        "libgui",
+        "libandroid_runtime",
+        "libnetd_client",
+    ],
+
+    static_libs: ["libstorage"],
+
+    include_dirs: ["bionic/libc/dns/include"],
+}
+
+// Network library.
+cc_library_shared {
+    name: "libandroid_net",
+    defaults: ["libandroid_defaults"],
+    srcs: ["net.c"],
+
+    shared_libs: ["libnetd_client"],
+
+    include_dirs: ["bionic/libc/dns/include"],
+}
diff --git a/native/android/Android.mk b/native/android/Android.mk
deleted file mode 100644
index 1f69df1..0000000
--- a/native/android/Android.mk
+++ /dev/null
@@ -1,66 +0,0 @@
-BASE_PATH := $(call my-dir)
-LOCAL_PATH:= $(call my-dir)
-
-common_cflags := -Wall -Werror -Wunused -Wunreachable-code
-
-include $(CLEAR_VARS)
-
-# our source files
-#
-LOCAL_SRC_FILES:= \
-    asset_manager.cpp \
-    choreographer.cpp \
-    configuration.cpp \
-    input.cpp \
-    looper.cpp \
-    native_activity.cpp \
-    native_window.cpp \
-    net.c \
-    obb.cpp \
-    sensor.cpp \
-    storage_manager.cpp \
-    trace.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
-    liblog \
-    libcutils \
-    libandroidfw \
-    libinput \
-    libutils \
-    libbinder \
-    libui \
-    libgui \
-    libandroid_runtime \
-    libnetd_client \
-
-LOCAL_STATIC_LIBRARIES := \
-    libstorage
-
-LOCAL_C_INCLUDES += \
-    frameworks/base/native/include \
-    frameworks/base/core/jni/android \
-    bionic/libc/dns/include \
-    system/netd/include \
-
-LOCAL_MODULE := libandroid
-
-LOCAL_CFLAGS += $(common_cflags)
-
-include $(BUILD_SHARED_LIBRARY)
-
-# Network library.
-include $(CLEAR_VARS)
-LOCAL_MODULE := libandroid_net
-LOCAL_CFLAGS := $(common_cflags)
-LOCAL_SRC_FILES:= \
-    net.c \
-
-LOCAL_SHARED_LIBRARIES := \
-    libnetd_client \
-
-LOCAL_C_INCLUDES += \
-    frameworks/base/native/include \
-    bionic/libc/dns/include \
-    system/netd/include \
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 17feb53..d456950 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -14,7 +14,7 @@
 
 // The headers module is in frameworks/native/Android.bp.
 ndk_library {
-    name: "libjnigraphics.ndk",
+    name: "libjnigraphics",
     symbol_file: "libjnigraphics.map.txt",
     first_version: "9",
     unversioned_until: "current",
diff --git a/obex/javax/obex/ApplicationParameter.java b/obex/javax/obex/ApplicationParameter.java
index a62210f..16770a1a 100644
--- a/obex/javax/obex/ApplicationParameter.java
+++ b/obex/javax/obex/ApplicationParameter.java
@@ -55,7 +55,7 @@
 
         public static final byte LISTSTARTOFFSET_TAGID = 0x05;
 
-        public static final byte FILTER_TAGID = 0x06;
+        public static final byte PROPERTY_SELECTOR_TAGID = 0x06;
 
         public static final byte FORMAT_TAGID = 0x07;
 
@@ -64,6 +64,20 @@
 
         // only used in "mch" in response
         public static final byte NEWMISSEDCALLS_TAGID = 0x09;
+
+        public static final byte SUPPORTEDFEATURE_TAGID = 0x10;
+
+        public static final byte PRIMARYVERSIONCOUNTER_TAGID = 0x0A;
+
+        public static final byte SECONDARYVERSIONCOUNTER_TAGID = 0x0B;
+
+        public static final byte VCARDSELECTOR_TAGID = 0x0C;
+
+        public static final byte DATABASEIDENTIFIER_TAGID = 0x0D;
+
+        public static final byte VCARDSELECTOROPERATOR_TAGID = 0x0E;
+
+        public static final byte RESET_NEW_MISSED_CALLS_TAGID = 0x0F;
     }
 
     public static class TRIPLET_VALUE {
@@ -99,13 +113,27 @@
 
         public static final byte LISTSTARTOFFSET_LENGTH = 2;
 
-        public static final byte FILTER_LENGTH = 8;
+        public static final byte PROPERTY_SELECTOR_LENGTH = 8;
 
         public static final byte FORMAT_LENGTH = 1;
 
         public static final byte PHONEBOOKSIZE_LENGTH = 2;
 
         public static final byte NEWMISSEDCALLS_LENGTH = 1;
+
+        public static final byte SUPPORTEDFEATURE_LENGTH = 4;
+
+        public static final byte PRIMARYVERSIONCOUNTER_LENGTH = 16;
+
+        public static final byte SECONDARYVERSIONCOUNTER_LENGTH = 16;
+
+        public static final byte VCARDSELECTOR_LENGTH = 8;
+
+        public static final byte DATABASEIDENTIFIER_LENGTH = 16;
+
+        public static final byte VCARDSELECTOROPERATOR_LENGTH = 1;
+
+        public static final byte RESETNEWMISSEDCALLS_LENGTH = 1;
     }
 
     public ApplicationParameter() {
diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java
index 3831cf7..dbfeefd 100644
--- a/obex/javax/obex/ServerSession.java
+++ b/obex/javax/obex/ServerSession.java
@@ -104,7 +104,6 @@
 
                     case ObexHelper.OBEX_OPCODE_DISCONNECT:
                         handleDisconnectRequest();
-                        done = true;
                         break;
 
                     case ObexHelper.OBEX_OPCODE_GET:
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 6394c64..34465e9 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -122,6 +122,11 @@
         WebSettings webSettings = myWebView.getSettings();
         webSettings.setJavaScriptEnabled(true);
         webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
+        webSettings.setUseWideViewPort(true);
+        webSettings.setLoadWithOverviewMode(true);
+        webSettings.setSupportZoom(true);
+        webSettings.setBuiltInZoomControls(true);
+        webSettings.setDisplayZoomControls(false);
         mWebViewClient = new MyWebViewClient();
         myWebView.setWebViewClient(mWebViewClient);
         myWebView.setWebChromeClient(new MyWebChromeClient());
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index e450283..2ef1cf5 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -33,6 +33,7 @@
         <receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver">
             <intent-filter>
                 <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
+                <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_RESET" />
             </intent-filter>
         </receiver>
         <service android:name="com.android.carrierdefaultapp.ProvisionObserver"
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index ec4c00e..61b3122 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -96,6 +96,10 @@
         WebSettings webSettings = mWebView.getSettings();
         webSettings.setJavaScriptEnabled(true);
         webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
+        webSettings.setUseWideViewPort(true);
+        webSettings.setLoadWithOverviewMode(true);
+        webSettings.setSupportZoom(true);
+        webSettings.setBuiltInZoomControls(true);
         mWebViewClient = new MyWebViewClient();
         mWebView.setWebViewClient(mWebViewClient);
         mWebView.setWebChromeClient(new MyWebChromeClient());
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index e1125d9..d5d0b79 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -91,6 +91,10 @@
                     arg1 = intent.getStringExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY);
                     arg2 = intent.getStringExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY);
                     break;
+                case TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET:
+                    configs = b.getStringArray(CarrierConfigManager
+                            .KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET);
+                    break;
                 default:
                     Rlog.e(TAG, "load carrier config failure with un-configured key: " +
                             intent.getAction());
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index c51f872..adbcfe8 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -429,6 +429,8 @@
     <string name="mobile_data_always_on">Cellular data always active</string>
     <!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
     <string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
+    <!-- Setting Checkbox title for enabling Bluetooth inband ringing -->
+    <string name="bluetooth_enable_inband_ringing">Enable in-band ringing</string>
 
     <!-- UI debug setting: Select Bluetooth AVRCP Version -->
     <string name="bluetooth_select_avrcp_version_string">Bluetooth AVRCP Version</string>
@@ -510,6 +512,9 @@
     <string name="verify_apps_over_usb_summary">Check apps installed via ADB/ADT for harmful behavior.</string>
     <!-- Summary of checkbox for disabling Bluetooth absolute volume -->
     <string name="bluetooth_disable_absolute_volume_summary">Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control.</string>
+    <!-- Summary of checkbox for enabling Bluetooth inband ringing -->
+    <string name="bluetooth_enable_inband_ringing_summary">Allow ringtones on the phone to be played on Bluetooth headsets</string>
+
 
     <!-- Title of checkbox setting that enables the terminal app. [CHAR LIMIT=32] -->
     <string name="enable_terminal_title">Local terminal</string>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index bf48e5d..75ae835 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -62,11 +62,12 @@
      */
     private static final ArraySet<String> sBroadcastOnRestore;
     static {
-        sBroadcastOnRestore = new ArraySet<String>(4);
+        sBroadcastOnRestore = new ArraySet<String>(5);
         sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
         sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS);
         sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
         sBroadcastOnRestore.add(Settings.Secure.ENABLED_INPUT_METHODS);
+        sBroadcastOnRestore.add(Settings.Global.BLUETOOTH_ON);
     }
 
     private interface SettingsLookup {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index cdfdad4..267dd3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -188,9 +188,16 @@
         if (mIcon == null) {
             return false;
         }
-        Drawable drawable = getIcon(mIcon);
+        Drawable drawable;
+        try {
+            drawable = getIcon(mIcon);
+        } catch (OutOfMemoryError e) {
+            Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot);
+            return false;
+        }
+
         if (drawable == null) {
-            Log.w(TAG, "No icon for slot " + mSlot);
+            Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
             return false;
         }
         if (withClear) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index a2d1baf..c726189 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -47,6 +47,11 @@
         public void onPhoneStateChanged(int phoneState) {
             update();
         }
+
+        @Override
+        public void onRefreshCarrierInfo() {
+            update();
+        }
     };
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 03c46e8..024c1a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -223,8 +223,7 @@
 
         String contentDescription = getStringIfExists(getContentDescription());
         String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
-        final boolean dataDisabled = mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED
-                && mCurrentState.userSetup;
+        final boolean dataDisabled = isDataDisabled() && mCurrentState.userSetup;
 
         // Show icon in QS when we are connected or data is disabled.
         boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;
@@ -404,7 +403,7 @@
         mCurrentState.roaming = isRoaming();
         if (isCarrierNetworkChangeActive()) {
             mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
-        } else if (isDataDisabled()) {
+        } else if (isDataDisabled() && !mConfig.alwaysShowDataRatIcon) {
             mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
         }
         if (isEmergencyOnly() != mCurrentState.isEmergency) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 2f7cd00..dd1a4c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -28,7 +28,9 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.PersistableBundle;
 import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -219,6 +221,7 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         mContext.registerReceiver(this, filter, null, mReceiverHandler);
         mListening = true;
 
@@ -396,6 +399,14 @@
                 // emergency state.
                 recalculateEmergency();
             }
+        } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
+            mConfig = Config.readConfig(mContext);
+            mReceiverHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    handleConfigurationChanged();
+                }
+            });
         } else {
             int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -904,6 +915,7 @@
         boolean show4gForLte = false;
         boolean hideLtePlus = false;
         boolean hspaDataDistinguishable;
+        boolean alwaysShowDataRatIcon = false;
 
         static Config readConfig(Context context) {
             Config config = new Config();
@@ -916,6 +928,14 @@
             config.hspaDataDistinguishable =
                     res.getBoolean(R.bool.config_hspa_data_distinguishable);
             config.hideLtePlus = res.getBoolean(R.bool.config_hideLtePlus);
+
+            CarrierConfigManager configMgr = (CarrierConfigManager)
+                    context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            PersistableBundle b = configMgr.getConfig();
+            if (b != null) {
+                config.alwaysShowDataRatIcon = b.getBoolean(
+                        CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
+            }
             return config;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index fce1172..93e1e6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -110,6 +110,21 @@
         verifyDataIndicators(0, 0);
     }
 
+    public void testAlwaysShowDataRatIcon() {
+        setupDefaultSignal();
+        Mockito.when(mMockTm.getDataEnabled(mSubId)).thenReturn(false);
+        updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED,
+                TelephonyManager.NETWORK_TYPE_GSM);
+
+        // Switch to showing data RAT icon when data is diconnected
+        // and re-initialize the NetworkController.
+        mConfig.alwaysShowDataRatIcon = true;
+        mNetworkController.handleConfigurationChanged();
+
+        verifyDataIndicators(TelephonyIcons.DATA_G[1][0 /* No direction */],
+                TelephonyIcons.QS_DATA_G);
+    }
+
     public void test4gDataIconConfigChange() {
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java
index 05ad161..238bf0f 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -2895,6 +2895,7 @@
             mAllocationArray[0] = createTyped(rs, t, usage);
             if ((usage & USAGE_IO_INPUT) != 0) {
                 if (numAlloc > MAX_NUMBER_IO_INPUT_ALLOC) {
+                    mAllocationArray[0].destroy();
                     throw new RSIllegalArgumentException("Exceeds the max number of Allocations allowed: " +
                                                          MAX_NUMBER_IO_INPUT_ALLOC);
                 }
diff --git a/rs/java/android/renderscript/ScriptIntrinsicBlur.java b/rs/java/android/renderscript/ScriptIntrinsicBlur.java
index 7a702e8..a36873e 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicBlur.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicBlur.java
@@ -59,6 +59,9 @@
      * @param ain The input allocation
      */
     public void setInput(Allocation ain) {
+        if (ain.getType().getY() == 0) {
+            throw new RSIllegalArgumentException("Input set to a 1D Allocation");
+        }
         mInput = ain;
         setVar(1, ain);
     }
@@ -85,6 +88,9 @@
      *             type.
      */
     public void forEach(Allocation aout) {
+        if (aout.getType().getY() == 0) {
+            throw new RSIllegalArgumentException("Output is a 1D Allocation");
+        }
         forEach(0, (Allocation) null, aout, null);
     }
 
@@ -97,6 +103,9 @@
      * @param opt LaunchOptions for clipping
      */
     public void forEach(Allocation aout, Script.LaunchOptions opt) {
+        if (aout.getType().getY() == 0) {
+            throw new RSIllegalArgumentException("Output is a 1D Allocation");
+        }
         forEach(0, (Allocation) null, aout, null, opt);
     }
 
diff --git a/rs/java/android/renderscript/ScriptIntrinsicLUT.java b/rs/java/android/renderscript/ScriptIntrinsicLUT.java
index 69ff64a..e90462d 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicLUT.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicLUT.java
@@ -56,6 +56,10 @@
 
     }
 
+    public void destroy() {
+        mTables.destroy();
+        super.destroy();
+    }
 
     private void validate(int index, int value) {
         if (index < 0 || index > 255) {
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 898eb7e..f9b0d2f 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -18,10 +18,18 @@
 LOCAL_AIDL_INCLUDES += \
     system/netd/server/binder
 
-LOCAL_JAVA_LIBRARIES := services.net telephony-common
-LOCAL_STATIC_JAVA_LIBRARIES := tzdata_shared2 tzdata_update2
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
 
+LOCAL_JAVA_LIBRARIES := \
+    services.net \
+    android.hidl.manager-V1.0-java \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    tzdata_shared2 \
+    tzdata_update2 \
+    android.hidl.base-V1.0-java-static \
+    android.hardware.tetheroffload.control-V1.0-java-static \
+
 ifneq ($(INCREMENTAL_BUILDS),)
     LOCAL_PROGUARD_ENABLED := disabled
     LOCAL_JACK_ENABLED := incremental
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index de4734f..ba1befd 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -90,6 +90,7 @@
     private static final String REASON_SYSTEM_BOOT = "system boot";
     private static final String REASON_UNEXPECTED = "unexpected crash";
     private static final String REASON_USER_SWITCH = "user switch";
+    private static final String REASON_RESTORE_USER_SETTING = "restore user setting";
 
     private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind
     //Maximum msec to wait for service restart
@@ -118,6 +119,10 @@
     private static final int MESSAGE_USER_UNLOCKED = 301;
     private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
     private static final int MESSAGE_BIND_PROFILE_SERVICE = 401;
+    private static final int MESSAGE_RESTORE_USER_SETTING = 500;
+
+    private static final int RESTORE_SETTING_TO_ON = 1;
+    private static final int RESTORE_SETTING_TO_OFF = 0;
 
     private static final int MAX_SAVE_RETRIES = 3;
     private static final int MAX_ERROR_RESTART_RETRIES = 6;
@@ -307,6 +312,34 @@
                 if (newName != null) {
                     storeNameAndAddress(newName, null);
                 }
+            } else if (BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED.equals(action)) {
+                String newAddress = intent.getStringExtra(BluetoothAdapter.EXTRA_BLUETOOTH_ADDRESS);
+                if (newAddress != null) {
+                    if (DBG) Slog.d(TAG, "Bluetooth Adapter address changed to " + newAddress);
+                    storeNameAndAddress(null, newAddress);
+                } else {
+                    if (DBG) Slog.e(TAG, "No Bluetooth Adapter address parameter found");
+                }
+            } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
+                final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+                if (Settings.Global.BLUETOOTH_ON.equals(name)) {
+                    // The Bluetooth On state may be changed during system restore.
+                    final String prevValue = intent.getStringExtra(
+                            Intent.EXTRA_SETTING_PREVIOUS_VALUE);
+                    final String newValue = intent.getStringExtra(
+                            Intent.EXTRA_SETTING_NEW_VALUE);
+
+                    if (DBG) Slog.d(TAG, "ACTION_SETTING_RESTORED with BLUETOOTH_ON, prevValue=" +
+                                    prevValue + ", newValue=" + newValue);
+
+                    if ((newValue != null) && (prevValue != null) && !prevValue.equals(newValue)) {
+                        Message msg = mHandler.obtainMessage(MESSAGE_RESTORE_USER_SETTING,
+                                                             newValue.equals("0") ?
+                                                             RESTORE_SETTING_TO_OFF :
+                                                             RESTORE_SETTING_TO_ON, 0);
+                        mHandler.sendMessage(msg);
+                    }
+                }
             }
         }
     };
@@ -340,9 +373,14 @@
         registerForBleScanModeChange();
         mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
         mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
-        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+        filter.addAction(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED);
+        filter.addAction(Intent.ACTION_SETTING_RESTORED);
         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiver(mReceiver, filter);
+
         loadStoredNameAndAddress();
         if (isBluetoothPersistedStateOn()) {
             if (DBG) Slog.d(TAG, "Startup: Bluetooth persisted state is ON.");
@@ -1413,6 +1451,20 @@
                     }
                     break;
 
+                case MESSAGE_RESTORE_USER_SETTING:
+                    try {
+                        if ((msg.arg1 == RESTORE_SETTING_TO_OFF) && mEnable) {
+                            if (DBG) Slog.d(TAG, "Restore Bluetooth state to disabled");
+                            disable(REASON_RESTORE_USER_SETTING, true);
+                        } else if ((msg.arg1 == RESTORE_SETTING_TO_ON) && !mEnable) {
+                            if (DBG) Slog.d(TAG, "Restore Bluetooth state to enabled");
+                            enable(REASON_RESTORE_USER_SETTING);
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG,"Unable to change Bluetooth On setting", e);
+                    }
+                    break;
+
                 case MESSAGE_REGISTER_ADAPTER:
                 {
                     IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
@@ -1485,16 +1537,6 @@
                             if (mGetNameAddressOnly) return;
                         }
 
-                        try {
-                            boolean enableHciSnoopLog = (Settings.Secure.getInt(mContentResolver,
-                                Settings.Secure.BLUETOOTH_HCI_LOG, 0) == 1);
-                            if (!mBluetooth.configHciSnoopLog(enableHciSnoopLog)) {
-                                Slog.e(TAG,"IBluetooth.configHciSnoopLog return false");
-                            }
-                        } catch (RemoteException e) {
-                            Slog.e(TAG,"Unable to call configHciSnoopLog", e);
-                        }
-
                         //Register callback object
                         try {
                             mBluetooth.registerCallback(mBluetoothCallback);
@@ -1657,7 +1699,7 @@
                     mHandler.removeMessages(MESSAGE_USER_SWITCHED);
 
                     /* disable and enable BT when detect a user switch */
-                    if (mEnable && mBluetooth != null) {
+                    if (mBluetooth != null && isEnabled()) {
                         try {
                             mBluetoothLock.readLock().lock();
                             if (mBluetooth != null) {
@@ -1726,6 +1768,8 @@
                         mState = BluetoothAdapter.STATE_OFF;
                         // enable
                         addActiveLog(REASON_USER_SWITCH, true);
+                        // mEnable flag could have been reset on disableBLE. Reenable it.
+                        mEnable = true;
                         handleEnable(mQuietEnable);
                     } else if (mBinding || mBluetooth != null) {
                         Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 9dde3e2..7b4981e4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -36,6 +36,7 @@
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
 import static android.net.NetworkPolicyManager.uidRulesToString;
+import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.Nullable;
 import android.app.BroadcastOptions;
@@ -48,7 +49,6 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.PacketKeepalive;
@@ -62,12 +62,14 @@
 import android.net.Network;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
+import android.net.MatchAllNetworkSpecifier;
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkMisc;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.Proxy;
@@ -91,10 +93,12 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -107,7 +111,6 @@
 import android.util.LocalLog;
 import android.util.LocalLog.ReadOnlyLocalLog;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -142,6 +145,7 @@
 import com.android.server.connectivity.PacManager;
 import com.android.server.connectivity.PermissionMonitor;
 import com.android.server.connectivity.Tethering;
+import com.android.server.connectivity.tethering.TetheringDependencies;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.net.LockdownVpnTracker;
@@ -179,6 +183,9 @@
         implements PendingIntent.OnFinished {
     private static final String TAG = ConnectivityService.class.getSimpleName();
 
+    public static final String DIAG_ARG = "--diag";
+    public static final String SHORT_ARG = "--short";
+
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
@@ -375,11 +382,6 @@
     private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
 
     /**
-     * used to specify whether a network should not be penalized when it becomes unvalidated.
-     */
-    private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
-
-    /**
      * used to ask the user to confirm a connection to an unvalidated network.
      * obj  = network
      */
@@ -396,6 +398,16 @@
      */
     private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
 
+    /**
+     * used to specify whether a network should not be penalized when it becomes unvalidated.
+     */
+    private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
+
+    /**
+     * used to trigger revalidation of a network.
+     */
+    private static final int EVENT_REVALIDATE_NETWORK = 36;
+
     private static String eventName(int what) {
         return sMagicDecoderRing.get(what, Integer.toString(what));
     }
@@ -479,7 +491,7 @@
             new ArrayDeque<ValidationLog>(MAX_VALIDATION_LOGS);
 
     private void addValidationLogs(ReadOnlyLocalLog log, Network network, String networkExtraInfo) {
-        synchronized(mValidationLogs) {
+        synchronized (mValidationLogs) {
             while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
                 mValidationLogs.removeLast();
             }
@@ -794,8 +806,7 @@
         mTestMode = mSystemProperties.get("cm.test.mode").equals("true")
                 && mSystemProperties.get("ro.build.type").equals("eng");
 
-        mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager,
-                                   IoThread.get().getLooper(), new MockableSystemProperties());
+        mTethering = makeTethering();
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
@@ -845,6 +856,14 @@
         mMultinetworkPolicyTracker.start();
     }
 
+    private Tethering makeTethering() {
+        // TODO: Move other elements into @Overridden getters.
+        final TetheringDependencies deps = new TetheringDependencies();
+        return new Tethering(mContext, mNetd, mStatsService, mPolicyManager,
+                IoThread.get().getLooper(), new MockableSystemProperties(),
+                deps);
+    }
+
     private NetworkRequest createInternetRequestForTransport(
             int transportType, NetworkRequest.Type type) {
         NetworkCapabilities netCap = new NetworkCapabilities();
@@ -868,8 +887,8 @@
     }
 
     private void handleMobileDataAlwaysOn() {
-        final boolean enable = (Settings.Global.getInt(
-                mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1) == 1);
+        final boolean enable = toBool(Settings.Global.getInt(
+                mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1));
         final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
         if (enable == isEnabled) {
             return;  // Nothing to do.
@@ -1663,7 +1682,7 @@
     }
 
     private void sendStickyBroadcast(Intent intent) {
-        synchronized(this) {
+        synchronized (this) {
             if (!mSystemReady) {
                 mInitialBroadcast = new Intent(intent);
             }
@@ -1704,7 +1723,7 @@
     void systemReady() {
         loadGlobalProxy();
 
-        synchronized(this) {
+        synchronized (this) {
             mSystemReady = true;
             if (mInitialBroadcast != null) {
                 mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
@@ -1945,7 +1964,7 @@
             return;
         }
 
-        if (argsContain(args, "--diag")) {
+        if (argsContain(args, DIAG_ARG)) {
             dumpNetworkDiagnostics(pw);
             return;
         }
@@ -2037,7 +2056,7 @@
         pw.println();
         dumpAvoidBadWifiSettings(pw);
 
-        if (argsContain(args, "--short") == false) {
+        if (argsContain(args, SHORT_ARG) == false) {
             pw.println();
             synchronized (mValidationLogs) {
                 pw.println("mValidationLogs (most recent first):");
@@ -2222,7 +2241,7 @@
                 }
                 case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
                     final int netId = msg.arg2;
-                    final boolean visible = (msg.arg1 != 0);
+                    final boolean visible = toBool(msg.arg1);
                     final NetworkAgentInfo nai;
                     synchronized (mNetworkForNetId) {
                         nai = mNetworkForNetId.get(netId);
@@ -2548,25 +2567,32 @@
     }
 
     private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
-        if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get(
-                nri.request.requestId) == null) {
-            handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_UNAVAIL);
+        if (mNetworkRequests.get(nri.request) == null) {
+            return;
         }
+        if (mNetworkForRequestId.get(nri.request.requestId) != null) {
+            return;
+        }
+        if (VDBG || (DBG && nri.request.isRequest())) {
+            log("releasing " + nri.request + " (timeout)");
+        }
+        handleRemoveNetworkRequest(nri);
+        callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
     }
 
     private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
-        final NetworkRequestInfo nri = getNriForAppRequest(
-                request, callingUid, "release NetworkRequest");
-        if (nri != null) {
-            handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_RELEASED);
+        final NetworkRequestInfo nri =
+                getNriForAppRequest(request, callingUid, "release NetworkRequest");
+        if (nri == null) {
+            return;
         }
+        if (VDBG || (DBG && nri.request.isRequest())) {
+            log("releasing " + nri.request + " (release request)");
+        }
+        handleRemoveNetworkRequest(nri);
     }
 
-    private void handleRemoveNetworkRequest(final NetworkRequestInfo nri, final int whichCallback) {
-        final String logCallbackType = ConnectivityManager.getCallbackName(whichCallback);
-        if (VDBG || (DBG && nri.request.isRequest())) {
-            log("releasing " + nri.request + " (" + logCallbackType + ")");
-        }
+    private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
         nri.unlinkDeathRecipient();
         mNetworkRequests.remove(nri.request);
         synchronized (mUidToNetworkRequestCount) {
@@ -2662,14 +2688,13 @@
                 }
             }
         }
-        callCallbackForRequest(nri, null, whichCallback, 0);
     }
 
     @Override
     public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
         enforceConnectivityInternalPermission();
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
-                accept ? 1 : 0, always ? 1: 0, network));
+                encodeBool(accept), encodeBool(always), network));
     }
 
     @Override
@@ -2706,7 +2731,7 @@
 
         if (always) {
             nai.asyncChannel.sendMessage(
-                    NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0);
+                    NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, encodeBool(accept));
         }
 
         if (!accept) {
@@ -2739,6 +2764,17 @@
                 PROMPT_UNVALIDATED_DELAY_MS);
     }
 
+    @Override
+    public void startCaptivePortalApp(Network network) {
+        enforceConnectivityInternalPermission();
+        mHandler.post(() -> {
+            NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+            if (nai == null) return;
+            if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return;
+            nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+        });
+    }
+
     public boolean avoidBadWifi() {
         return mMultinetworkPolicyTracker.getAvoidBadWifi();
     }
@@ -2907,7 +2943,8 @@
                     break;
                 }
                 case EVENT_SET_ACCEPT_UNVALIDATED: {
-                    handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
+                    Network network = (Network) msg.obj;
+                    handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
                     break;
                 }
                 case EVENT_SET_AVOID_UNVALIDATED: {
@@ -2941,14 +2978,18 @@
                     }
                     break;
                 }
+                case EVENT_REVALIDATE_NETWORK: {
+                    handleReportNetworkConnectivity((Network) msg.obj, msg.arg1, toBool(msg.arg2));
+                    break;
+                }
             }
         }
     }
 
     // javadoc from interface
     @Override
-    public int tether(String iface) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public int tether(String iface, String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         if (isTetheringSupported()) {
             final int status = mTethering.tether(iface);
             return status;
@@ -2959,8 +3000,8 @@
 
     // javadoc from interface
     @Override
-    public int untether(String iface) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public int untether(String iface, String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
 
         if (isTetheringSupported()) {
             final int status = mTethering.untether(iface);
@@ -3014,8 +3055,8 @@
     }
 
     @Override
-    public int setUsbTethering(boolean enable) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public int setUsbTethering(boolean enable, String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         if (isTetheringSupported()) {
             return mTethering.setUsbTethering(enable);
         } else {
@@ -3055,9 +3096,10 @@
     @Override
     public boolean isTetheringSupported() {
         enforceTetherAccessPermission();
-        int defaultVal = (mSystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
-        boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
+        int defaultVal = encodeBool(!mSystemProperties.get("ro.tether.denied").equals("true"));
+        boolean tetherSupported = toBool(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.TETHER_SUPPORTED, defaultVal));
+        boolean tetherEnabledInSettings = tetherSupported
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
 
         // Elevate to system UID to avoid caller requiring MANAGE_USERS permission.
@@ -3069,13 +3111,13 @@
             Binder.restoreCallingIdentity(token);
         }
 
-        return tetherEnabledInSettings && adminUser &&
-               mTethering.hasTetherableConfiguration();
+        return tetherEnabledInSettings && adminUser && mTethering.hasTetherableConfiguration();
     }
 
     @Override
-    public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi,
+            String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         if (!isTetheringSupported()) {
             receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
             return;
@@ -3084,8 +3126,8 @@
     }
 
     @Override
-    public void stopTethering(int type) {
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+    public void stopTethering(int type, String callerPkg) {
+        ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
         mTethering.stopTethering(type);
     }
 
@@ -3147,8 +3189,14 @@
     public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
         enforceAccessPermission();
         enforceInternetPermission();
+        final int uid = Binder.getCallingUid();
+        final int connectivityInfo = encodeBool(hasConnectivity);
+        mHandler.sendMessage(
+                mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network));
+    }
 
-        // TODO: execute this logic on ConnectivityService handler.
+    private void handleReportNetworkConnectivity(
+            Network network, int uid, boolean hasConnectivity) {
         final NetworkAgentInfo nai;
         if (network == null) {
             nai = getDefaultNetwork();
@@ -3163,10 +3211,9 @@
         if (hasConnectivity == nai.lastValidated) {
             return;
         }
-        final int uid = Binder.getCallingUid();
         if (DBG) {
-            log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity +
-                    ") by " + uid);
+            int netid = nai.network.netId;
+            log("reportNetworkConnectivity(" + netid + ", " + hasConnectivity + ") by " + uid);
         }
         // Validating a network that has not yet connected could result in a call to
         // rematchNetworkAndRequests() which is not meant to work on such networks.
@@ -3449,13 +3496,6 @@
         Slog.e(TAG, s);
     }
 
-    private static <T> T checkNotNull(T value, String message) {
-        if (value == null) {
-            throw new NullPointerException(message);
-        }
-        return value;
-    }
-
     /**
      * Prepare for a VPN application.
      * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
@@ -3476,7 +3516,7 @@
         enforceCrossUserPermission(userId);
         throwIfLockdownEnabled();
 
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn vpn = mVpns.get(userId);
             if (vpn != null) {
                 return vpn.prepare(oldPackage, newPackage);
@@ -3503,7 +3543,7 @@
     public void setVpnPackageAuthorization(String packageName, int userId, boolean authorized) {
         enforceCrossUserPermission(userId);
 
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn vpn = mVpns.get(userId);
             if (vpn != null) {
                 vpn.setPackageAuthorization(packageName, authorized);
@@ -3522,7 +3562,7 @@
     public ParcelFileDescriptor establishVpn(VpnConfig config) {
         throwIfLockdownEnabled();
         int user = UserHandle.getUserId(Binder.getCallingUid());
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             return mVpns.get(user).establish(config);
         }
     }
@@ -3539,7 +3579,7 @@
             throw new IllegalStateException("Missing active network connection");
         }
         int user = UserHandle.getUserId(Binder.getCallingUid());
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
         }
     }
@@ -3553,7 +3593,7 @@
     public LegacyVpnInfo getLegacyVpnInfo(int userId) {
         enforceCrossUserPermission(userId);
 
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             return mVpns.get(userId).getLegacyVpnInfo();
         }
     }
@@ -3569,7 +3609,7 @@
             return new VpnInfo[0];
         }
 
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             List<VpnInfo> infoList = new ArrayList<>();
             for (int i = 0; i < mVpns.size(); i++) {
                 VpnInfo info = createVpnInfo(mVpns.valueAt(i));
@@ -3617,7 +3657,7 @@
     @Override
     public VpnConfig getVpnConfig(int userId) {
         enforceCrossUserPermission(userId);
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn vpn = mVpns.get(userId);
             if (vpn != null) {
                 return vpn.getVpnConfig();
@@ -3651,7 +3691,7 @@
                 return true;
             }
             int user = UserHandle.getUserId(Binder.getCallingUid());
-            synchronized(mVpns) {
+            synchronized (mVpns) {
                 Vpn vpn = mVpns.get(user);
                 if (vpn == null) {
                     Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
@@ -3678,17 +3718,9 @@
             existing.shutdown();
         }
 
-        try {
-            if (tracker != null) {
-                mNetd.setFirewallEnabled(true);
-                mNetd.setFirewallInterfaceRule("lo", true);
-                mLockdownTracker = tracker;
-                mLockdownTracker.init();
-            } else {
-                mNetd.setFirewallEnabled(false);
-            }
-        } catch (RemoteException e) {
-            // ignored; NMS lives inside system_server
+        if (tracker != null) {
+            mLockdownTracker = tracker;
+            mLockdownTracker.init();
         }
     }
 
@@ -3884,7 +3916,7 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             final ContentResolver cr = mContext.getContentResolver();
-            Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0);
+            Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, encodeBool(enable));
             Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
             intent.putExtra("state", enable);
             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
@@ -3894,7 +3926,7 @@
     }
 
     private void onUserStart(int userId) {
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn userVpn = mVpns.get(userId);
             if (userVpn != null) {
                 loge("Starting user already has a VPN");
@@ -3909,7 +3941,7 @@
     }
 
     private void onUserStop(int userId) {
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             Vpn userVpn = mVpns.get(userId);
             if (userVpn == null) {
                 loge("Stopped user has no VPN");
@@ -3921,7 +3953,7 @@
     }
 
     private void onUserAdded(int userId) {
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             final int vpnsSize = mVpns.size();
             for (int i = 0; i < vpnsSize; i++) {
                 Vpn vpn = mVpns.valueAt(i);
@@ -3931,7 +3963,7 @@
     }
 
     private void onUserRemoved(int userId) {
-        synchronized(mVpns) {
+        synchronized (mVpns) {
             final int vpnsSize = mVpns.size();
             for (int i = 0; i < vpnsSize; i++) {
                 Vpn vpn = mVpns.valueAt(i);
@@ -4055,7 +4087,8 @@
             synchronized (mUidToNetworkRequestCount) {
                 int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
                 if (networkRequests >= MAX_NETWORK_REQUESTS_PER_UID) {
-                    throw new IllegalArgumentException("Too many NetworkRequests filed");
+                    throw new ServiceSpecificException(
+                            ConnectivityManager.Errors.TOO_MANY_REQUESTS);
                 }
                 mUidToNetworkRequestCount.put(mUid, networkRequests);
             }
@@ -4121,6 +4154,18 @@
                 0, 0, thresholds);
     }
 
+    private void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
+        if (nc == null) {
+            return;
+        }
+        NetworkSpecifier ns = nc.getNetworkSpecifier();
+        if (ns == null) {
+            return;
+        }
+        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns);
+        ns.assertValidFromUid(Binder.getCallingUid());
+    }
+
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
@@ -4146,12 +4191,7 @@
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Bad timeout specified");
         }
-
-        if (NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER
-                .equals(networkCapabilities.getNetworkSpecifier())) {
-            throw new IllegalArgumentException("Invalid network specifier - must not be '"
-                    + NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER + "'");
-        }
+        ensureValidNetworkSpecifier(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), type);
@@ -4223,6 +4263,7 @@
         enforceNetworkRequestPermissions(networkCapabilities);
         enforceMeteredApnPolicy(networkCapabilities);
         ensureRequestableCapabilities(networkCapabilities);
+        ensureValidNetworkSpecifier(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -4284,6 +4325,7 @@
             // can't request networks.
             nc.addCapability(NET_CAPABILITY_FOREGROUND);
         }
+        ensureValidNetworkSpecifier(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
@@ -4301,6 +4343,7 @@
         if (!hasWifiNetworkListenPermission(networkCapabilities)) {
             enforceAccessPermission();
         }
+        ensureValidNetworkSpecifier(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(
                 new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId(),
@@ -4573,17 +4616,24 @@
         int last = 0;
         for (InetAddress dns : dnses) {
             ++last;
-            String key = "net.dns" + last;
-            String value = dns.getHostAddress();
-            mSystemProperties.set(key, value);
+            setNetDnsProperty(last, dns.getHostAddress());
         }
         for (int i = last + 1; i <= mNumDnsEntries; ++i) {
-            String key = "net.dns" + i;
-            mSystemProperties.set(key, "");
+            setNetDnsProperty(i, "");
         }
         mNumDnsEntries = last;
     }
 
+    private void setNetDnsProperty(int which, String value) {
+        final String key = "net.dns" + which;
+        // Log and forget errors setting unsupported properties.
+        try {
+            mSystemProperties.set(key, value);
+        } catch (Exception e) {
+            Log.e(TAG, "Error setting unsupported net.dns property: ", e);
+        }
+    }
+
     private String getNetworkPermission(NetworkCapabilities nc) {
         // TODO: make these permission strings AIDL constants instead.
         if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
@@ -4714,16 +4764,17 @@
         releasePendingNetworkRequestWithDelay(pendingIntent);
     }
 
-    private void callCallbackForRequest(NetworkRequestInfo nri,
+    private static void callCallbackForRequest(NetworkRequestInfo nri,
             NetworkAgentInfo networkAgent, int notificationType, int arg1) {
-        if (nri.messenger == null) return;  // Default request has no msgr
+        if (nri.messenger == null) {
+            return;  // Default request has no msgr
+        }
         Bundle bundle = new Bundle();
-        bundle.putParcelable(NetworkRequest.class.getSimpleName(),
-                new NetworkRequest(nri.request));
+        // TODO: check if defensive copies of data is needed.
+        putParcelable(bundle, new NetworkRequest(nri.request));
         Message msg = Message.obtain();
-        if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL &&
-                notificationType != ConnectivityManager.CALLBACK_RELEASED) {
-            bundle.putParcelable(Network.class.getSimpleName(), networkAgent.network);
+        if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
+            putParcelable(bundle, networkAgent.network);
         }
         switch (notificationType) {
             case ConnectivityManager.CALLBACK_LOSING: {
@@ -4731,13 +4782,11 @@
                 break;
             }
             case ConnectivityManager.CALLBACK_CAP_CHANGED: {
-                bundle.putParcelable(NetworkCapabilities.class.getSimpleName(),
-                        new NetworkCapabilities(networkAgent.networkCapabilities));
+                putParcelable(bundle, new NetworkCapabilities(networkAgent.networkCapabilities));
                 break;
             }
             case ConnectivityManager.CALLBACK_IP_CHANGED: {
-                bundle.putParcelable(LinkProperties.class.getSimpleName(),
-                        new LinkProperties(networkAgent.linkProperties));
+                putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
                 break;
             }
         }
@@ -4745,8 +4794,8 @@
         msg.setData(bundle);
         try {
             if (VDBG) {
-                log("sending notification " + notifyTypeToName(notificationType) +
-                        " for " + nri.request);
+                String notification = ConnectivityManager.getCallbackName(notificationType);
+                log("sending notification " + notification + " for " + nri.request);
             }
             nri.messenger.send(msg);
         } catch (RemoteException e) {
@@ -4755,6 +4804,10 @@
         }
     }
 
+    private static <T extends Parcelable> void putParcelable(Bundle bundle, T t) {
+        bundle.putParcelable(t.getClass().getSimpleName(), t);
+    }
+
     private void teardownUnneededNetwork(NetworkAgentInfo nai) {
         if (nai.numRequestNetworkRequests() != 0) {
             for (int i = 0; i < nai.numNetworkRequests(); i++) {
@@ -5356,7 +5409,10 @@
     }
 
     protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType, int arg1) {
-        if (VDBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name());
+        if (VDBG) {
+            String notification = ConnectivityManager.getCallbackName(notifyType);
+            log("notifyType " + notification + " for " + networkAgent.name());
+        }
         for (int i = 0; i < networkAgent.numNetworkRequests(); i++) {
             NetworkRequest nr = networkAgent.requestAt(i);
             NetworkRequestInfo nri = mNetworkRequests.get(nr);
@@ -5375,20 +5431,6 @@
         notifyNetworkCallbacks(networkAgent, notifyType, 0);
     }
 
-    private String notifyTypeToName(int notifyType) {
-        switch (notifyType) {
-            case ConnectivityManager.CALLBACK_PRECHECK:    return "PRECHECK";
-            case ConnectivityManager.CALLBACK_AVAILABLE:   return "AVAILABLE";
-            case ConnectivityManager.CALLBACK_LOSING:      return "LOSING";
-            case ConnectivityManager.CALLBACK_LOST:        return "LOST";
-            case ConnectivityManager.CALLBACK_UNAVAIL:     return "UNAVAILABLE";
-            case ConnectivityManager.CALLBACK_CAP_CHANGED: return "CAP_CHANGED";
-            case ConnectivityManager.CALLBACK_IP_CHANGED:  return "IP_CHANGED";
-            case ConnectivityManager.CALLBACK_RELEASED:    return "RELEASED";
-        }
-        return "UNKNOWN";
-    }
-
     /**
      * Notify NetworkStatsService that the set of active ifaces has changed, or that one of the
      * properties tracked by NetworkStatsService on an active iface has changed.
@@ -5468,8 +5510,9 @@
 
         if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
             // Untether
+            String pkgName = mContext.getOpPackageName();
             for (String tether : getTetheredIfaces()) {
-                untether(tether);
+                untether(tether, pkgName);
             }
         }
 
@@ -5554,4 +5597,12 @@
     private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
         mMetricsLog.log(new NetworkEvent(nai.network.netId, evtype));
     }
+
+    private static boolean toBool(int encodedBoolean) {
+        return encodedBoolean != 0; // Only 0 means false.
+    }
+
+    private static int encodeBool(boolean b) {
+        return b ? 1 : 0;
+    }
 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 87938cb..ac2f4d0 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -95,7 +95,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.NativeDaemonConnector.Command;
 import com.android.server.NativeDaemonConnector.SensitiveArg;
-import com.android.server.net.LockdownVpnTracker;
 import com.google.android.collect.Maps;
 
 import java.io.BufferedReader;
@@ -150,7 +149,7 @@
      */
     public static final String PERMISSION_SYSTEM = "SYSTEM";
 
-    class NetdResponseCode {
+    static class NetdResponseCode {
         /* Keep in sync with system/netd/server/ResponseCode.h */
         public static final int InterfaceListResult       = 110;
         public static final int TetherInterfaceListResult = 111;
@@ -221,7 +220,7 @@
 
     private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
 
-    private Object mQuotaLock = new Object();
+    private final Object mQuotaLock = new Object();
 
     /** Set of interfaces with active quotas. */
     @GuardedBy("mQuotaLock")
@@ -266,7 +265,7 @@
     @GuardedBy("mQuotaLock")
     private boolean mDataSaverMode;
 
-    private Object mIdleTimerLock = new Object();
+    private final Object mIdleTimerLock = new Object();
     /** Set of interfaces with active idle timers. */
     private static class IdleTimerParams {
         public final int timeout;
@@ -628,7 +627,7 @@
                 }
             }
 
-            setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
+            setFirewallEnabled(mFirewallEnabled);
 
             syncFirewallChainLocked(FIREWALL_CHAIN_NONE, mUidFirewallRules, "");
             syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, mUidFirewallStandbyRules, "standby ");
@@ -1897,30 +1896,6 @@
         }
     }
 
-    @Override
-    public void setFirewallEgressSourceRule(String addr, boolean allow) {
-        enforceSystemUid();
-        Preconditions.checkState(mFirewallEnabled);
-        final String rule = allow ? "allow" : "deny";
-        try {
-            mConnector.execute("firewall", "set_egress_source_rule", addr, rule);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
-    public void setFirewallEgressDestRule(String addr, int port, boolean allow) {
-        enforceSystemUid();
-        Preconditions.checkState(mFirewallEnabled);
-        final String rule = allow ? "allow" : "deny";
-        try {
-            mConnector.execute("firewall", "set_egress_dest_rule", addr, port, rule);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
     private void closeSocketsForFirewallChainLocked(int chain, String chainName) {
         // UID ranges to close sockets on.
         UidRange[] ranges;
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index b64c65d..9f7437e 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -26,6 +26,8 @@
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -73,6 +75,7 @@
     private long mNitzTimeSetTime = NOT_SET;
     // TODO: Have a way to look up the timezone we are in
     private long mNitzZoneSetTime = NOT_SET;
+    private Network mDefaultNetwork = null;
 
     private Context mContext;
     private TrustedTime mTime;
@@ -82,6 +85,8 @@
     private AlarmManager mAlarmManager;
     private PendingIntent mPendingPollIntent;
     private SettingsObserver mSettingsObserver;
+    private ConnectivityManager mCM;
+    private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback;
     // The last time that we successfully fetched the NTP time.
     private long mLastNtpFetchTime = NOT_SET;
     private final PowerManager.WakeLock mWakeLock;
@@ -103,6 +108,7 @@
         mContext = context;
         mTime = NtpTrustedTime.getInstance(context);
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         Intent pollIntent = new Intent(ACTION_POLL, null);
         mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
 
@@ -123,13 +129,12 @@
     public void systemRunning() {
         registerForTelephonyIntents();
         registerForAlarms();
-        registerForConnectivityIntents();
 
         HandlerThread thread = new HandlerThread(TAG);
         thread.start();
         mHandler = new MyHandler(thread.getLooper());
-        // Check the network time on the new thread
-        mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
+        mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
+        mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);
 
         mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
         mSettingsObserver.observe(mContext);
@@ -152,15 +157,10 @@
             }, new IntentFilter(ACTION_POLL));
     }
 
-    private void registerForConnectivityIntents() {
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        mContext.registerReceiver(mConnectivityReceiver, intentFilter);
-    }
-
     private void onPollNetworkTime(int event) {
-        // If Automatic time is not set, don't bother.
-        if (!isAutomaticTimeRequested()) return;
+        // If Automatic time is not set, don't bother. Similarly, if we don't
+        // have any default network, don't bother.
+        if (!isAutomaticTimeRequested() || mDefaultNetwork == null) return;
         mWakeLock.acquire();
         try {
             onPollNetworkTimeUnderWakeLock(event);
@@ -262,22 +262,6 @@
         }
     };
 
-    /** Receiver for ConnectivityManager events */
-    private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
-                if (DBG) Log.d(TAG, "Received CONNECTIVITY_ACTION ");
-                // Don't bother checking if we have connectivity, NtpTrustedTime does that for us.
-                Message message = mHandler.obtainMessage(EVENT_NETWORK_CHANGED);
-                // Send with a short delay to make sure the network is ready for use
-                mHandler.sendMessageDelayed(message, NETWORK_CHANGE_EVENT_DELAY_MS);
-            }
-        }
-    };
-
     /** Handler to do the network accesses on */
     private class MyHandler extends Handler {
 
@@ -297,6 +281,21 @@
         }
     }
 
+    private class NetworkTimeUpdateCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(Network network) {
+            Log.d(TAG, String.format("New default network %s; checking time.", network));
+            mDefaultNetwork = network;
+            // Running on mHandler so invoke directly.
+            onPollNetworkTime(EVENT_NETWORK_CHANGED);
+        }
+
+        @Override
+        public void onLost(Network network) {
+            if (network.equals(mDefaultNetwork)) mDefaultNetwork = null;
+        }
+    }
+
     /** Observer to watch for changes to the AUTO_TIME setting */
     private static class SettingsObserver extends ContentObserver {
 
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index a691af9..2158f60 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -26,6 +26,8 @@
 import android.net.nsd.INsdManager;
 import android.net.nsd.NsdManager;
 import android.os.Binder;
+import android.os.HandlerThread;
+import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.UserHandle;
@@ -33,18 +35,20 @@
 import android.util.Base64;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.net.InetAddress;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
-import com.android.server.NativeDaemonConnector.Command;
 
 /**
  * Network Service Discovery Service handles remote service discovery operation requests by
@@ -59,10 +63,10 @@
     private static final boolean DBG = true;
 
     private final Context mContext;
-    private final ContentResolver mContentResolver;
+    private final NsdSettings mNsdSettings;
     private final NsdStateMachine mNsdStateMachine;
-    private final NativeDaemonConnector mNativeConnector;
-    private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
+    private final DaemonConnection mDaemon;
+    private final NativeCallbackReceiver mDaemonCallback;
 
     /**
      * Clients receiving asynchronous messages
@@ -95,11 +99,7 @@
             ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
                 @Override
                 public void onChange(boolean selfChange) {
-                    if (isNsdEnabled()) {
-                        mNsdStateMachine.sendMessage(NsdManager.ENABLE);
-                    } else {
-                        mNsdStateMachine.sendMessage(NsdManager.DISABLE);
-                    }
+                    notifyEnabled(isNsdEnabled());
                 }
             };
 
@@ -108,16 +108,13 @@
                     false, contentObserver);
         }
 
-        NsdStateMachine(String name) {
-            super(name);
+        NsdStateMachine(String name, Handler handler) {
+            super(name, handler);
             addState(mDefaultState);
                 addState(mDisabledState, mDefaultState);
                 addState(mEnabledState, mDefaultState);
-            if (isNsdEnabled()) {
-                setInitialState(mEnabledState);
-            } else {
-                setInitialState(mDisabledState);
-            }
+            State initialState = isNsdEnabled() ? mEnabledState : mDisabledState;
+            setInitialState(initialState);
             setLogRecSize(25);
             registerForNsdSetting();
         }
@@ -157,7 +154,7 @@
                         }
                         //Last client
                         if (mClients.size() == 0) {
-                            stopMDnsDaemon();
+                            mDaemon.stop();
                         }
                         break;
                     case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
@@ -217,14 +214,14 @@
             public void enter() {
                 sendNsdStateChangeBroadcast(true);
                 if (mClients.size() > 0) {
-                    startMDnsDaemon();
+                    mDaemon.start();
                 }
             }
 
             @Override
             public void exit() {
                 if (mClients.size() > 0) {
-                    stopMDnsDaemon();
+                    mDaemon.stop();
                 }
             }
 
@@ -243,8 +240,8 @@
             }
 
             private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
-                clientInfo.mClientIds.remove(clientId);
-                clientInfo.mClientRequests.remove(clientId);
+                clientInfo.mClientIds.delete(clientId);
+                clientInfo.mClientRequests.delete(clientId);
                 mIdToClientInfoMap.remove(globalId);
             }
 
@@ -258,7 +255,7 @@
                         //First client
                         if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL &&
                                 mClients.size() == 0) {
-                            startMDnsDaemon();
+                            mDaemon.start();
                         }
                         return NOT_HANDLED;
                     case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
@@ -297,7 +294,7 @@
                         clientInfo = mClients.get(msg.replyTo);
 
                         try {
-                            id = clientInfo.mClientIds.get(msg.arg2).intValue();
+                            id = clientInfo.mClientIds.get(msg.arg2);
                         } catch (NullPointerException e) {
                             replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
                                     NsdManager.FAILURE_INTERNAL_ERROR);
@@ -335,7 +332,7 @@
                         if (DBG) Slog.d(TAG, "unregister service");
                         clientInfo = mClients.get(msg.replyTo);
                         try {
-                            id = clientInfo.mClientIds.get(msg.arg2).intValue();
+                            id = clientInfo.mClientIds.get(msg.arg2);
                         } catch (NullPointerException e) {
                             replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
                                     NsdManager.FAILURE_INTERNAL_ERROR);
@@ -541,57 +538,55 @@
         return sb.toString();
     }
 
-    private NsdService(Context context) {
-        mContext = context;
-        mContentResolver = context.getContentResolver();
-
-        mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
-                MDNS_TAG, 25, null);
-
-        mNsdStateMachine = new NsdStateMachine(TAG);
+    @VisibleForTesting
+    NsdService(Context ctx, NsdSettings settings, Handler handler, DaemonConnectionSupplier fn) {
+        mContext = ctx;
+        mNsdSettings = settings;
+        mNsdStateMachine = new NsdStateMachine(TAG, handler);
         mNsdStateMachine.start();
-
-        Thread th = new Thread(mNativeConnector, MDNS_TAG);
-        th.start();
+        mDaemonCallback = new NativeCallbackReceiver();
+        mDaemon = fn.get(mDaemonCallback);
     }
 
     public static NsdService create(Context context) throws InterruptedException {
-        NsdService service = new NsdService(context);
-        service.mNativeDaemonConnected.await();
+        NsdSettings settings = NsdSettings.makeDefault(context);
+        HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        Handler handler = new Handler(thread.getLooper());
+        NsdService service = new NsdService(context, settings, handler, DaemonConnection::new);
+        service.mDaemonCallback.awaitConnection();
         return service;
     }
 
     public Messenger getMessenger() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET,
-            "NsdService");
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
         return new Messenger(mNsdStateMachine.getHandler());
     }
 
-    public void setEnabled(boolean enable) {
+    public void setEnabled(boolean isEnabled) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
                 "NsdService");
-        Settings.Global.putInt(mContentResolver, Settings.Global.NSD_ON, enable ? 1 : 0);
-        if (enable) {
-            mNsdStateMachine.sendMessage(NsdManager.ENABLE);
-        } else {
-            mNsdStateMachine.sendMessage(NsdManager.DISABLE);
-        }
+        mNsdSettings.putEnabledStatus(isEnabled);
+        notifyEnabled(isEnabled);
     }
 
-    private void sendNsdStateChangeBroadcast(boolean enabled) {
+    private void notifyEnabled(boolean isEnabled) {
+        mNsdStateMachine.sendMessage(isEnabled ? NsdManager.ENABLE : NsdManager.DISABLE);
+    }
+
+    private void sendNsdStateChangeBroadcast(boolean isEnabled) {
         final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        if (enabled) {
-            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_ENABLED);
-        } else {
-            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED);
-        }
+        int nsdState = isEnabled ? NsdManager.NSD_STATE_ENABLED : NsdManager.NSD_STATE_DISABLED;
+        intent.putExtra(NsdManager.EXTRA_NSD_STATE, nsdState);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     private boolean isNsdEnabled() {
-        boolean ret = Settings.Global.getInt(mContentResolver, Settings.Global.NSD_ON, 1) == 1;
-        if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret);
+        boolean ret = mNsdSettings.isEnabled();
+        if (DBG) {
+            Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
+        }
         return ret;
     }
 
@@ -655,14 +650,23 @@
     }
 
     class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
-        public void onDaemonConnected() {
-            mNativeDaemonConnected.countDown();
+        private final CountDownLatch connected = new CountDownLatch(1);
+
+        public void awaitConnection() throws InterruptedException {
+            connected.await();
         }
 
+        @Override
+        public void onDaemonConnected() {
+            connected.countDown();
+        }
+
+        @Override
         public boolean onCheckHoldWakeLock(int code) {
             return false;
         }
 
+        @Override
         public boolean onEvent(int code, String raw, String[] cooked) {
             // TODO: NDC translates a message to a callback, we could enhance NDC to
             // directly interact with a state machine through messages
@@ -672,132 +676,88 @@
         }
     }
 
-    private boolean startMDnsDaemon() {
-        if (DBG) Slog.d(TAG, "startMDnsDaemon");
-        try {
-            mNativeConnector.execute("mdnssd", "start-service");
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to start daemon" + e);
-            return false;
-        }
-        return true;
+    interface DaemonConnectionSupplier {
+        DaemonConnection get(NativeCallbackReceiver callback);
     }
 
-    private boolean stopMDnsDaemon() {
-        if (DBG) Slog.d(TAG, "stopMDnsDaemon");
-        try {
-            mNativeConnector.execute("mdnssd", "stop-service");
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to start daemon" + e);
-            return false;
+    @VisibleForTesting
+    public static class DaemonConnection {
+        final NativeDaemonConnector mNativeConnector;
+
+        DaemonConnection(NativeCallbackReceiver callback) {
+            mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null);
+            new Thread(mNativeConnector, MDNS_TAG).start();
         }
-        return true;
+
+        public boolean execute(Object... args) {
+            if (DBG) {
+                Slog.d(TAG, "mdnssd " + Arrays.toString(args));
+            }
+            try {
+                mNativeConnector.execute("mdnssd", args);
+            } catch (NativeDaemonConnectorException e) {
+                Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
+                return false;
+            }
+            return true;
+        }
+
+        public void start() {
+            execute("start-service");
+        }
+
+        public void stop() {
+            execute("stop-service");
+        }
     }
 
     private boolean registerService(int regId, NsdServiceInfo service) {
-        if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
-        try {
-            Command cmd = new Command("mdnssd", "register", regId, service.getServiceName(),
-                    service.getServiceType(), service.getPort(),
-                    Base64.encodeToString(service.getTxtRecord(), Base64.DEFAULT)
-                            .replace("\n", ""));
-
-            mNativeConnector.execute(cmd);
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to execute registerService " + e);
-            return false;
+        if (DBG) {
+            Slog.d(TAG, "registerService: " + regId + " " + service);
         }
-        return true;
+        String name = service.getServiceName();
+        String type = service.getServiceType();
+        int port = service.getPort();
+        byte[] textRecord = service.getTxtRecord();
+        String record = Base64.encodeToString(textRecord, Base64.DEFAULT).replace("\n", "");
+        return mDaemon.execute("register", regId, name, type, port, record);
     }
 
     private boolean unregisterService(int regId) {
-        if (DBG) Slog.d(TAG, "unregisterService: " + regId);
-        try {
-            mNativeConnector.execute("mdnssd", "stop-register", regId);
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to execute unregisterService " + e);
-            return false;
-        }
-        return true;
+        return mDaemon.execute("stop-register", regId);
     }
 
     private boolean updateService(int regId, DnsSdTxtRecord t) {
-        if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t);
-        try {
-            if (t == null) return false;
-            mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData());
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to updateServices " + e);
+        if (t == null) {
             return false;
         }
-        return true;
+        return mDaemon.execute("update", regId, t.size(), t.getRawData());
     }
 
     private boolean discoverServices(int discoveryId, String serviceType) {
-        if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType);
-        try {
-            mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType);
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to discoverServices " + e);
-            return false;
-        }
-        return true;
+        return mDaemon.execute("discover", discoveryId, serviceType);
     }
 
     private boolean stopServiceDiscovery(int discoveryId) {
-        if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId);
-        try {
-            mNativeConnector.execute("mdnssd", "stop-discover", discoveryId);
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to stopServiceDiscovery " + e);
-            return false;
-        }
-        return true;
+        return mDaemon.execute("stop-discover", discoveryId);
     }
 
     private boolean resolveService(int resolveId, NsdServiceInfo service) {
-        if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service);
-        try {
-            mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(),
-                    service.getServiceType(), "local.");
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to resolveService " + e);
-            return false;
-        }
-        return true;
+        String name = service.getServiceName();
+        String type = service.getServiceType();
+        return mDaemon.execute("resolve", resolveId, name, type, "local.");
     }
 
     private boolean stopResolveService(int resolveId) {
-        if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId);
-        try {
-            mNativeConnector.execute("mdnssd", "stop-resolve", resolveId);
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to stop resolve " + e);
-            return false;
-        }
-        return true;
+        return mDaemon.execute("stop-resolve", resolveId);
     }
 
     private boolean getAddrInfo(int resolveId, String hostname) {
-        if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId);
-        try {
-            mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname);
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to getAddrInfo " + e);
-            return false;
-        }
-        return true;
+        return mDaemon.execute("getaddrinfo", resolveId, hostname);
     }
 
     private boolean stopGetAddrInfo(int resolveId) {
-        if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId);
-        try {
-            mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId);
-        } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to stopGetAddrInfo " + e);
-            return false;
-        }
-        return true;
+        return mDaemon.execute("stop-getaddrinfo", resolveId);
     }
 
     @Override
@@ -859,10 +819,10 @@
         private NsdServiceInfo mResolvedService;
 
         /* A map from client id to unique id sent to mDns */
-        private final SparseArray<Integer> mClientIds = new SparseArray<>();
+        private final SparseIntArray mClientIds = new SparseIntArray();
 
         /* A map from client id to the type of the request we had received */
-        private final SparseArray<Integer> mClientRequests = new SparseArray<>();
+        private final SparseIntArray mClientRequests = new SparseIntArray();
 
         private ClientInfo(AsyncChannel c, Messenger m) {
             mChannel = c;
@@ -889,6 +849,7 @@
         // and send cancellations to the daemon.
         private void expungeAllRequests() {
             int globalId, clientId, i;
+            // TODO: to keep handler responsive, do not clean all requests for that client at once.
             for (i = 0; i < mClientIds.size(); i++) {
                 clientId = mClientIds.keyAt(i);
                 globalId = mClientIds.valueAt(i);
@@ -916,15 +877,32 @@
         // mClientIds is a sparse array of listener id -> mDnsClient id.  For a given mDnsClient id,
         // return the corresponding listener id.  mDnsClient id is also called a global id.
         private int getClientId(final int globalId) {
-            // This doesn't use mClientIds.indexOfValue because indexOfValue uses == (not .equals)
-            // while also coercing the int primitives to Integer objects.
-            for (int i = 0, nSize = mClientIds.size(); i < nSize; i++) {
-                int mDnsId = mClientIds.valueAt(i);
-                if (globalId == mDnsId) {
-                    return mClientIds.keyAt(i);
-                }
+            int idx = mClientIds.indexOfValue(globalId);
+            if (idx < 0) {
+                return idx;
             }
-            return -1;
+            return mClientIds.keyAt(idx);
+        }
+    }
+
+    @VisibleForTesting
+    public interface NsdSettings {
+        boolean isEnabled();
+        void putEnabledStatus(boolean isEnabled);
+
+        static NsdSettings makeDefault(Context context) {
+            ContentResolver resolver = context.getContentResolver();
+            return new NsdSettings() {
+                @Override
+                public boolean isEnabled() {
+                    return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1;
+                }
+
+                @Override
+                public void putEnabledStatus(boolean isEnabled) {
+                    Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0);
+                }
+            };
         }
     }
 }
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index fa5a52c..1657364 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -243,20 +243,22 @@
 
         // get the path to the odex or oat file
         String baseCodePath = cameraInfo.getBaseCodePath();
-        String odex = null;
+        String[] files = null;
         try {
-            odex = DexFile.getDexFileOutputPath(baseCodePath, arch);
+            files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
         } catch (IOException ioe) {}
-        if (odex == null) {
+        if (files == null) {
             return true;
         }
 
         //not pinning the oat/odex is not a fatal error
-        pf = pinFile(odex, 0, 0, MAX_CAMERA_PIN_SIZE);
-        if (pf != null) {
-            mPinnedCameraFiles.add(pf);
-            if (DEBUG) {
-                Slog.i(TAG, "Pinned " + pf.mFilename);
+        for (String file : files) {
+            pf = pinFile(file, 0, 0, MAX_CAMERA_PIN_SIZE);
+            if (pf != null) {
+                mPinnedCameraFiles.add(pf);
+                if (DEBUG) {
+                    Slog.i(TAG, "Pinned " + pf.mFilename);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 82e6b42..1cada64 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -57,9 +57,8 @@
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.internal.telephony.IPhoneStateListener;
-import com.android.internal.telephony.DefaultPhoneNotifier;
+import com.android.internal.telephony.PhoneConstantConversions;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.server.am.BatteryStatsService;
 
@@ -155,10 +154,6 @@
 
     private int[] mDataConnectionState;
 
-    private boolean[] mDataConnectionPossible;
-
-    private String[] mDataConnectionReason;
-
     private String[] mDataConnectionApn;
 
     private ArrayList<String>[] mConnectedApns;
@@ -171,7 +166,7 @@
 
     private int[] mDataConnectionNetworkType;
 
-    private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN;
+    private int mOtaspMode = TelephonyManager.OTASP_UNKNOWN;
 
     private ArrayList<List<CellInfo>> mCellInfo = null;
 
@@ -308,8 +303,6 @@
         mDataActivationState = new int[numPhones];
         mSignalStrength = new SignalStrength[numPhones];
         mMessageWaiting = new boolean[numPhones];
-        mDataConnectionPossible = new boolean[numPhones];
-        mDataConnectionReason = new String[numPhones];
         mDataConnectionApn = new String[numPhones];
         mCallForwarding = new boolean[numPhones];
         mCellLocation = new Bundle[numPhones];
@@ -327,8 +320,6 @@
             mSignalStrength[i] =  new SignalStrength();
             mMessageWaiting[i] =  false;
             mCallForwarding[i] =  false;
-            mDataConnectionPossible[i] = false;
-            mDataConnectionReason[i] =  "";
             mDataConnectionApn[i] =  "";
             mCellLocation[i] = new Bundle();
             mCellInfo.add(i, null);
@@ -1082,16 +1073,16 @@
         }
     }
 
-    public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
+    public void notifyDataConnection(int state, boolean isDataAllowed,
             String reason, String apn, String apnType, LinkProperties linkProperties,
             NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
         notifyDataConnectionForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, state,
-            isDataConnectivityPossible,reason, apn, apnType, linkProperties,
+            isDataAllowed,reason, apn, apnType, linkProperties,
             networkCapabilities, networkType, roaming);
     }
 
     public void notifyDataConnectionForSubscriber(int subId, int state,
-            boolean isDataConnectivityPossible, String reason, String apn, String apnType,
+            boolean isDataAllowed, String reason, String apn, String apnType,
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
             int networkType, boolean roaming) {
         if (!checkNotifyPermission("notifyDataConnection()" )) {
@@ -1099,7 +1090,7 @@
         }
         if (VDBG) {
             log("notifyDataConnectionForSubscriber: subId=" + subId
-                + " state=" + state + " isDataConnectivityPossible=" + isDataConnectivityPossible
+                + " state=" + state + " isDataAllowed=" + isDataAllowed
                 + " reason='" + reason
                 + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
                 + " mRecords.size()=" + mRecords.size());
@@ -1127,8 +1118,6 @@
                         }
                     }
                 }
-                mDataConnectionPossible[phoneId] = isDataConnectivityPossible;
-                mDataConnectionReason[phoneId] = reason;
                 mDataConnectionLinkProperties[phoneId] = linkProperties;
                 mDataConnectionNetworkCapabilities[phoneId] = networkCapabilities;
                 if (mDataConnectionNetworkType[phoneId] != networkType) {
@@ -1172,7 +1161,7 @@
             }
             handleRemoveListLocked();
         }
-        broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
+        broadcastDataConnectionStateChanged(state, isDataAllowed, reason, apn,
                 apnType, linkProperties, networkCapabilities, roaming, subId);
         broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn, reason,
                 linkProperties, "");
@@ -1413,8 +1402,6 @@
                 pw.println("  mCallForwarding=" + mCallForwarding[i]);
                 pw.println("  mDataActivity=" + mDataActivity[i]);
                 pw.println("  mDataConnectionState=" + mDataConnectionState[i]);
-                pw.println("  mDataConnectionPossible=" + mDataConnectionPossible[i]);
-                pw.println("  mDataConnectionReason=" + mDataConnectionReason[i]);
                 pw.println("  mDataConnectionApn=" + mDataConnectionApn[i]);
                 pw.println("  mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]);
                 pw.println("  mDataConnectionNetworkCapabilities=" +
@@ -1496,7 +1483,7 @@
 
         Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         intent.putExtra(PhoneConstants.STATE_KEY,
-                DefaultPhoneNotifier.convertCallState(state).toString());
+                PhoneConstantConversions.convertCallState(state).toString());
         if (!TextUtils.isEmpty(incomingNumber)) {
             intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber);
         }
@@ -1522,7 +1509,7 @@
     }
 
     private void broadcastDataConnectionStateChanged(int state,
-            boolean isDataConnectivityPossible,
+            boolean isDataAllowed,
             String reason, String apn, String apnType, LinkProperties linkProperties,
             NetworkCapabilities networkCapabilities, boolean roaming, int subId) {
         // Note: not reporting to the battery stats service here, because the
@@ -1530,8 +1517,8 @@
         // required info.
         Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
         intent.putExtra(PhoneConstants.STATE_KEY,
-                DefaultPhoneNotifier.convertDataState(state).toString());
-        if (!isDataConnectivityPossible) {
+                PhoneConstantConversions.convertDataState(state).toString());
+        if (!isDataAllowed) {
             intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
         }
         if (reason != null) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5e37d67..c5fc038 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5324,35 +5324,61 @@
         return tracesFile;
     }
 
+    public static class DumpStackFileObserver extends FileObserver {
+        // Keep in sync with frameworks/native/cmds/dumpstate/utils.cpp
+        private static final int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
+        static final int TRACE_DUMP_TIMEOUT_SECONDS = TRACE_DUMP_TIMEOUT_MS / 1000;
+
+        private final String mTracesPath;
+        private boolean mClosed;
+
+        public DumpStackFileObserver(String tracesPath) {
+            super(tracesPath, FileObserver.CLOSE_WRITE);
+            mTracesPath = tracesPath;
+        }
+
+        @Override
+        public synchronized void onEvent(int event, String path) {
+            mClosed = true;
+            notify();
+        }
+
+        public void dumpWithTimeout(int pid) {
+            Process.sendSignal(pid, Process.SIGNAL_QUIT);
+            synchronized (this) {
+                try {
+                    wait(TRACE_DUMP_TIMEOUT_MS); // Wait for traces file to be closed.
+                } catch (InterruptedException e) {
+                    Slog.wtf(TAG, e);
+                }
+            }
+            if (!mClosed) {
+                Slog.w(TAG, "Didn't see close of " + mTracesPath + " for pid " + pid +
+                       ". Attempting native stack collection.");
+                Debug.dumpNativeBacktraceToFileTimeout(pid, mTracesPath, TRACE_DUMP_TIMEOUT_SECONDS);
+            }
+            mClosed = false;
+        }
+    }
+
     private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
             ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
         // Use a FileObserver to detect when traces finish writing.
         // The order of traces is considered important to maintain for legibility.
-        FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
-            @Override
-            public synchronized void onEvent(int event, String path) { notify(); }
-        };
-
+        DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
         try {
             observer.startWatching();
 
             // First collect all of the stacks of the most important pids.
             if (firstPids != null) {
-                try {
-                    int num = firstPids.size();
-                    for (int i = 0; i < num; i++) {
-                        synchronized (observer) {
-                            if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
-                                    + firstPids.get(i));
-                            final long sime = SystemClock.elapsedRealtime();
-                            Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
-                            observer.wait(1000);  // Wait for write-close, give up after 1 sec
-                            if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
-                                    + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
-                        }
-                    }
-                } catch (InterruptedException e) {
-                    Slog.wtf(TAG, e);
+                int num = firstPids.size();
+                for (int i = 0; i < num; i++) {
+                    if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
+                            + firstPids.get(i));
+                    final long sime = SystemClock.elapsedRealtime();
+                    observer.dumpWithTimeout(firstPids.get(i));
+                    if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
+                            + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                 }
             }
 
@@ -5364,7 +5390,8 @@
                         if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
                         final long sime = SystemClock.elapsedRealtime();
 
-                        Debug.dumpNativeBacktraceToFileTimeout(pid, tracesPath, 10);
+                        Debug.dumpNativeBacktraceToFileTimeout(
+                                pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
                         if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
                                 + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                     }
@@ -5391,19 +5418,12 @@
                     ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
                     if (lastPids.indexOfKey(stats.pid) >= 0) {
                         numProcs++;
-                        try {
-                            synchronized (observer) {
-                                if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
-                                        + stats.pid);
-                                final long stime = SystemClock.elapsedRealtime();
-                                Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
-                                observer.wait(1000);  // Wait for write-close, give up after 1 sec
-                                if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
-                                        + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
-                            }
-                        } catch (InterruptedException e) {
-                            Slog.wtf(TAG, e);
-                        }
+                        if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
+                                + stats.pid);
+                        final long stime = SystemClock.elapsedRealtime();
+                        observer.dumpWithTimeout(stats.pid);
+                        if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
+                                + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
                     } else if (DEBUG_ANR) {
                         Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
                                 + stats.pid);
@@ -13829,6 +13849,7 @@
         // concurrently during execution of this method)
         synchronized (this) {
             sb.append("Process: ").append(processName).append("\n");
+            sb.append("PID: ").append(process.pid).append("\n");
             int flags = process.info.flags;
             IPackageManager pm = AppGlobals.getPackageManager();
             sb.append("Flags: 0x").append(Integer.toHexString(flags)).append("\n");
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 71c7fd3..2e74eb9 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -493,8 +493,9 @@
                                         Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                 runningIntent.setData(Uri.fromParts("package",
                                         appInfo.packageName, null));
-                                PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0,
-                                        runningIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                                PendingIntent pi = PendingIntent.getActivityAsUser(ams.mContext, 0,
+                                        runningIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
+                                        UserHandle.of(userId));
                                 notiBuilder.setColor(ams.mContext.getColor(
                                         com.android.internal
                                                 .R.color.system_notification_accent_color));
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 6cf8f37..66347e6 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -70,6 +70,7 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
@@ -90,6 +91,8 @@
     private static final String DEFAULT_HTTP_URL      =
             "http://connectivitycheck.gstatic.com/generate_204";
     private static final String DEFAULT_FALLBACK_URL  = "http://www.google.com/gen_204";
+    private static final String DEFAULT_OTHER_FALLBACK_URLS =
+            "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";
@@ -194,11 +197,13 @@
     public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10;
 
     /**
-     * Message to self indicating sign-in app should be launched.
+     * Message indicating sign-in app should be launched.
      * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
-     * user touches the sign in notification.
+     * user touches the sign in notification, or sent by
+     * ConnectivityService when the user touches the "sign into
+     * network" button in the wifi access point detail page.
      */
-    private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
+    public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
 
     /**
      * Retest network to see if captive portal is still in place.
@@ -259,6 +264,13 @@
     // This variable is set before transitioning to the mCaptivePortalState.
     private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
 
+    // Configuration values for captive portal detection probes.
+    private final String mCaptivePortalUserAgent;
+    private final URL mCaptivePortalHttpsUrl;
+    private final URL mCaptivePortalHttpUrl;
+    private final URL[] mCaptivePortalFallbackUrls;
+    private int mNextFallbackUrlIndex = 0;
+
     public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
             NetworkRequest defaultRequest) {
         this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog());
@@ -293,6 +305,11 @@
         mUseHttps = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
 
+        mCaptivePortalUserAgent = getCaptivePortalUserAgent(context);
+        mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl(context));
+        mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(context));
+        mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls(context);
+
         start();
     }
 
@@ -439,7 +456,7 @@
                     intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL,
                             mLastPortalProbeResult.detectUrl);
                     intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT,
-                            getCaptivePortalUserAgent(mContext));
+                            mCaptivePortalUserAgent);
                     intent.setFlags(
                             Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
                     mContext.startActivityAsUser(intent, UserHandle.CURRENT);
@@ -463,7 +480,11 @@
      */
     @VisibleForTesting
     public static final class CaptivePortalProbeResult {
-        static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(599);
+        static final int SUCCESS_CODE = 204;
+        static final int FAILED_CODE = 599;
+
+        static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(FAILED_CODE);
+        static final CaptivePortalProbeResult SUCCESS = new CaptivePortalProbeResult(SUCCESS_CODE);
 
         private final int mHttpResponseCode;  // HTTP response code returned from Internet probe.
         final String redirectUrl;             // Redirect destination returned from Internet probe.
@@ -481,9 +502,16 @@
             this(httpResponseCode, null, null);
         }
 
-        boolean isSuccessful() { return mHttpResponseCode == 204; }
+        boolean isSuccessful() {
+            return mHttpResponseCode == SUCCESS_CODE;
+        }
+
         boolean isPortal() {
-            return !isSuccessful() && mHttpResponseCode >= 200 && mHttpResponseCode <= 399;
+            return !isSuccessful() && (mHttpResponseCode >= 200) && (mHttpResponseCode <= 399);
+        }
+
+        boolean isFailed() {
+            return !isSuccessful() && !isPortal();
         }
     }
 
@@ -655,9 +683,24 @@
         return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
     }
 
-    private static String getCaptivePortalFallbackUrl(Context context) {
-        return getSetting(context,
+    private URL[] makeCaptivePortalFallbackUrls(Context context) {
+        String separator = ",";
+        String firstUrl = getSetting(context,
                 Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL);
+        String joinedUrls = firstUrl + separator + getSetting(context,
+                Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS, DEFAULT_OTHER_FALLBACK_URLS);
+        List<URL> urls = new ArrayList<>();
+        for (String s : joinedUrls.split(separator)) {
+            URL u = makeURL(s);
+            if (u == null) {
+                continue;
+            }
+            urls.add(u);
+        }
+        if (urls.isEmpty()) {
+            Log.e(TAG, String.format("could not create any url from %s", joinedUrls));
+        }
+        return urls.toArray(new URL[urls.size()]);
     }
 
     private static String getCaptivePortalUserAgent(Context context) {
@@ -669,14 +712,25 @@
         return value != null ? value : defaultValue;
     }
 
+    private URL nextFallbackUrl() {
+        if (mCaptivePortalFallbackUrls.length == 0) {
+            return null;
+        }
+        int idx = Math.abs(mNextFallbackUrlIndex) % mCaptivePortalFallbackUrls.length;
+        mNextFallbackUrlIndex += new Random().nextInt(); // randomely change url without memory.
+        return mCaptivePortalFallbackUrls[idx];
+    }
+
     @VisibleForTesting
     protected CaptivePortalProbeResult isCaptivePortal() {
         if (!mIsCaptivePortalCheckEnabled) {
             validationLog("Validation disabled.");
-            return new CaptivePortalProbeResult(204);
+            return CaptivePortalProbeResult.SUCCESS;
         }
 
-        URL pacUrl = null, httpsUrl = null, httpUrl = null, fallbackUrl = null;
+        URL pacUrl = null;
+        URL httpsUrl = mCaptivePortalHttpsUrl;
+        URL httpUrl = mCaptivePortalHttpUrl;
 
         // On networks with a PAC instead of fetching a URL that should result in a 204
         // response, we instead simply fetch the PAC script.  This is done for a few reasons:
@@ -703,13 +757,8 @@
             }
         }
 
-        if (pacUrl == null) {
-            httpsUrl = makeURL(getCaptivePortalServerHttpsUrl(mContext));
-            httpUrl = makeURL(getCaptivePortalServerHttpUrl(mContext));
-            fallbackUrl = makeURL(getCaptivePortalFallbackUrl(mContext));
-            if (httpUrl == null || httpsUrl == null) {
-                return CaptivePortalProbeResult.FAILED;
-            }
+        if ((pacUrl == null) && (httpUrl == null || httpsUrl == null)) {
+            return CaptivePortalProbeResult.FAILED;
         }
 
         long startTime = SystemClock.elapsedRealtime();
@@ -718,7 +767,7 @@
         if (pacUrl != null) {
             result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
         } else if (mUseHttps) {
-            result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl, fallbackUrl);
+            result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl);
         } else {
             result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
         }
@@ -780,7 +829,7 @@
     @VisibleForTesting
     protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType) {
         HttpURLConnection urlConnection = null;
-        int httpResponseCode = 599;
+        int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
         String redirectUrl = null;
         final Stopwatch probeTimer = new Stopwatch().start();
         try {
@@ -789,9 +838,8 @@
             urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
             urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
             urlConnection.setUseCaches(false);
-            final String userAgent = getCaptivePortalUserAgent(mContext);
-            if (userAgent != null) {
-                urlConnection.setRequestProperty("User-Agent", userAgent);
+            if (mCaptivePortalUserAgent != null) {
+                urlConnection.setRequestProperty("User-Agent", mCaptivePortalUserAgent);
             }
             // cannot read request header after connection
             String requestHeader = urlConnection.getRequestProperties().toString();
@@ -819,7 +867,7 @@
                 if (probeType == ValidationProbeEvent.PROBE_PAC) {
                     validationLog(
                             probeType, url, "PAC fetch 200 response interpreted as 204 response.");
-                    httpResponseCode = 204;
+                    httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
                 } else if (urlConnection.getContentLengthLong() == 0) {
                     // Consider 200 response with "Content-length=0" to not be a captive portal.
                     // There's no point in considering this a captive portal as the user cannot
@@ -827,20 +875,20 @@
                     // See http://b/9972012.
                     validationLog(probeType, url,
                         "200 response with Content-length=0 interpreted as 204 response.");
-                    httpResponseCode = 204;
+                    httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
                 } else if (urlConnection.getContentLengthLong() == -1) {
                     // When no Content-length (default value == -1), attempt to read a byte from the
                     // response. Do not use available() as it is unreliable. See http://b/33498325.
                     if (urlConnection.getInputStream().read() == -1) {
                         validationLog(
                                 probeType, url, "Empty 200 response interpreted as 204 response.");
-                        httpResponseCode = 204;
+                        httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
                     }
                 }
             }
         } catch (IOException e) {
-            validationLog(probeType, url, "Probably not a portal: exception " + e);
-            if (httpResponseCode == 599) {
+            validationLog(probeType, url, "Probe failed with exception " + e);
+            if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) {
                 // TODO: Ping gateway and DNS server and log results.
             }
         } finally {
@@ -853,7 +901,7 @@
     }
 
     private CaptivePortalProbeResult sendParallelHttpProbes(
-            ProxyInfo proxy, URL httpsUrl, URL httpUrl, URL fallbackUrl) {
+            ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
         // Number of probes to wait for. If a probe completes with a conclusive answer
         // it shortcuts the latch immediately by forcing the count to 0.
         final CountDownLatch latch = new CountDownLatch(2);
@@ -912,7 +960,8 @@
         if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
             return httpsResult;
         }
-        // If a fallback url is specified, use a fallback probe to try again portal detection.
+        // If a fallback url exists, use a fallback probe to try again portal detection.
+        URL fallbackUrl = nextFallbackUrl();
         if (fallbackUrl != null) {
             CaptivePortalProbeResult result =
                     sendHttpProbe(fallbackUrl, ValidationProbeEvent.PROBE_FALLBACK);
@@ -920,14 +969,18 @@
                 return result;
             }
         }
-        // Otherwise wait until https probe completes and use its result.
+        // Otherwise wait until http and https probes completes and use their results.
         try {
+            httpProbe.join();
+            if (httpProbe.result().isPortal()) {
+                return httpProbe.result();
+            }
             httpsProbe.join();
+            return httpsProbe.result();
         } catch (InterruptedException e) {
-            validationLog("Error: https probe wait interrupted!");
+            validationLog("Error: http or https probe wait interrupted!");
             return CaptivePortalProbeResult.FAILED;
         }
-        return httpsProbe.result();
     }
 
     private URL makeURL(String url) {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 3bf55965..ee89d57 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -20,6 +20,7 @@
 import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static com.android.server.ConnectivityService.SHORT_ARG;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -47,6 +48,7 @@
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.util.SharedLog;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Bundle;
@@ -77,8 +79,10 @@
 import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
 import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
 import com.android.server.connectivity.tethering.OffloadController;
+import com.android.server.connectivity.tethering.SimChangeListener;
 import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
 import com.android.server.connectivity.tethering.TetheringConfiguration;
+import com.android.server.connectivity.tethering.TetheringDependencies;
 import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
 import com.android.server.net.BaseNetworkObserver;
 
@@ -90,7 +94,9 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 
@@ -122,14 +128,27 @@
         public final TetherInterfaceStateMachine stateMachine;
         public int lastState;
         public int lastError;
+
         public TetherState(TetherInterfaceStateMachine sm) {
             stateMachine = sm;
             // Assume all state machines start out available and with no errors.
             lastState = IControlsTethering.STATE_AVAILABLE;
             lastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
         }
+
+        public boolean isCurrentlyServing() {
+            switch (lastState) {
+                case IControlsTethering.STATE_TETHERED:
+                case IControlsTethering.STATE_LOCAL_ONLY:
+                    return true;
+                default:
+                    return false;
+            }
+        }
     }
 
+    private final SharedLog mLog = new SharedLog(TAG);
+
     // used to synchronize public access to members
     private final Object mPublicSync;
     private final Context mContext;
@@ -143,11 +162,14 @@
     private final StateMachine mTetherMasterSM;
     private final OffloadController mOffloadController;
     private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+    private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;
+    private final SimChangeListener mSimChange;
 
     private volatile TetheringConfiguration mConfig;
     private String mCurrentUpstreamIface;
     private Notification.Builder mTetheredNotificationBuilder;
     private int mLastNotificationId;
+
     private boolean mRndisEnabled;       // track the RNDIS function enabled state
     private boolean mUsbTetherRequested; // true if USB tethering should be started
                                          // when RNDIS is enabled
@@ -156,7 +178,9 @@
 
     public Tethering(Context context, INetworkManagementService nmService,
             INetworkStatsService statsService, INetworkPolicyManager policyManager,
-            Looper looper, MockableSystemProperties systemProperties) {
+            Looper looper, MockableSystemProperties systemProperties,
+            TetheringDependencies deps) {
+        mLog.mark("constructed");
         mContext = context;
         mNMService = nmService;
         mStatsService = statsService;
@@ -171,9 +195,13 @@
         mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
         mTetherMasterSM.start();
 
-        mOffloadController = new OffloadController(mTetherMasterSM.getHandler());
+        mOffloadController = new OffloadController(mTetherMasterSM.getHandler(),
+                deps.getOffloadHardwareInterface(), mLog);
         mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
-                mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
+                mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK, mLog);
+        mForwardedDownstreams = new HashSet<>();
+        mSimChange = new SimChangeListener(
+                mContext, mTetherMasterSM.getHandler(), () -> reevaluateSimCardProvisioning());
 
         mStateReceiver = new StateReceiver();
         IntentFilter filter = new IntentFilter();
@@ -199,6 +227,10 @@
         return (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
+    private WifiManager getWifiManager() {
+        return (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+    }
+
     private void updateConfiguration() {
         mConfig = new TetheringConfiguration(mContext);
     }
@@ -209,21 +241,11 @@
         // See NetlinkHandler.cpp:71.
         if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
         synchronized (mPublicSync) {
-            int interfaceType = ifaceNameToType(iface);
-            if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
-                return;
-            }
-
-            TetherState tetherState = mTetherStates.get(iface);
             if (up) {
-                if (tetherState == null) {
-                    trackNewTetherableInterface(iface, interfaceType);
-                }
+                maybeTrackNewInterfaceLocked(iface);
             } else {
-                if (interfaceType == ConnectivityManager.TETHERING_BLUETOOTH) {
-                    tetherState.stateMachine.sendMessage(
-                            TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
-                    mTetherStates.remove(iface);
+                if (ifaceNameToType(iface) == ConnectivityManager.TETHERING_BLUETOOTH) {
+                    stopTrackingInterfaceLocked(iface);
                 } else {
                     // Ignore usb0 down after enabling RNDIS.
                     // We will handle disconnect in interfaceRemoved.
@@ -257,18 +279,7 @@
     public void interfaceAdded(String iface) {
         if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
         synchronized (mPublicSync) {
-            int interfaceType = ifaceNameToType(iface);
-            if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
-                if (VDBG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
-                return;
-            }
-
-            TetherState tetherState = mTetherStates.get(iface);
-            if (tetherState == null) {
-                trackNewTetherableInterface(iface, interfaceType);
-            } else {
-                if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
-            }
+            maybeTrackNewInterfaceLocked(iface);
         }
     }
 
@@ -276,15 +287,7 @@
     public void interfaceRemoved(String iface) {
         if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
         synchronized (mPublicSync) {
-            TetherState tetherState = mTetherStates.get(iface);
-            if (tetherState == null) {
-                if (VDBG) {
-                    Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
-                }
-                return;
-            }
-            tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
-            mTetherStates.remove(iface);
+            stopTrackingInterfaceLocked(iface);
         }
     }
 
@@ -336,6 +339,20 @@
         return (provisionApp.length == 2);
     }
 
+    // Used by the SIM card change observation code.
+    // TODO: De-duplicate above code.
+    private boolean hasMobileHotspotProvisionApp() {
+        try {
+            if (!mContext.getResources().getString(com.android.internal.R.string.
+                    config_mobile_hotspot_provision_app_no_ui).isEmpty()) {
+                Log.d(TAG, "re-evaluate provisioning");
+                return true;
+            }
+        } catch (Resources.NotFoundException e) {}
+        Log.d(TAG, "no prov-check needed for new SIM");
+        return false;
+    }
+
     /**
      * Enables or disables tethering for the given type. This should only be called once
      * provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks
@@ -375,15 +392,21 @@
     }
 
     private int setWifiTethering(final boolean enable) {
-        synchronized (mPublicSync) {
-            mWifiTetherRequested = enable;
-            final WifiManager wifiManager =
-                    (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
-            if (wifiManager.setWifiApEnabled(null /* use existing wifi config */, enable)) {
-                return ConnectivityManager.TETHER_ERROR_NO_ERROR;
+        int rval = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mPublicSync) {
+                mWifiTetherRequested = enable;
+                final WifiManager mgr = getWifiManager();
+                if ((enable && mgr.startSoftAp(null /* use existing wifi config */)) ||
+                    (!enable && mgr.stopSoftAp())) {
+                    rval = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+                }
             }
-            return ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
+        return rval;
     }
 
     private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
@@ -510,7 +533,21 @@
         }
     }
 
+    // Used by the SIM card change observation code.
+    // TODO: De-duplicate with above code, where possible.
+    private void startProvisionIntent(int tetherType) {
+        final Intent startProvIntent = new Intent();
+        startProvIntent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
+        startProvIntent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
+        startProvIntent.setComponent(TETHER_SERVICE);
+        mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
+    }
+
     public int tether(String iface) {
+        return tether(iface, IControlsTethering.STATE_TETHERED);
+    }
+
+    private int tether(String iface, int requestedState) {
         if (DBG) Log.d(TAG, "Tethering " + iface);
         synchronized (mPublicSync) {
             TetherState tetherState = mTetherStates.get(iface);
@@ -524,7 +561,13 @@
                 Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring");
                 return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
             }
-            tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+            // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's
+            // queue but not yet processed, this will be a no-op and it will not
+            // return an error.
+            //
+            // TODO: reexamine the threading and messaging model.
+            tetherState.stateMachine.sendMessage(
+                    TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, requestedState);
             return ConnectivityManager.TETHER_ERROR_NO_ERROR;
         }
     }
@@ -537,8 +580,8 @@
                 Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
                 return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
             }
-            if (tetherState.lastState != IControlsTethering.STATE_TETHERED) {
-                Log.e(TAG, "Tried to untether an untethered iface :" + iface + ", ignoring");
+            if (!tetherState.isCurrentlyServing()) {
+                Log.e(TAG, "Tried to untether an inactive iface :" + iface + ", ignoring");
                 return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
             }
             tetherState.stateMachine.sendMessage(
@@ -565,12 +608,14 @@
         }
     }
 
+    // TODO: Figure out how to update for local hotspot mode interfaces.
     private void sendTetherStateChangedBroadcast() {
         if (!getConnectivityManager().isTetheringSupported()) return;
 
-        ArrayList<String> availableList = new ArrayList<String>();
-        ArrayList<String> activeList = new ArrayList<String>();
-        ArrayList<String> erroredList = new ArrayList<String>();
+        final ArrayList<String> availableList = new ArrayList<>();
+        final ArrayList<String> tetherList = new ArrayList<>();
+        final ArrayList<String> localOnlyList = new ArrayList<>();
+        final ArrayList<String> erroredList = new ArrayList<>();
 
         boolean wifiTethered = false;
         boolean usbTethered = false;
@@ -586,6 +631,8 @@
                     erroredList.add(iface);
                 } else if (tetherState.lastState == IControlsTethering.STATE_AVAILABLE) {
                     availableList.add(iface);
+                } else if (tetherState.lastState == IControlsTethering.STATE_LOCAL_ONLY) {
+                    localOnlyList.add(iface);
                 } else if (tetherState.lastState == IControlsTethering.STATE_TETHERED) {
                     if (cfg.isUsb(iface)) {
                         usbTethered = true;
@@ -594,25 +641,25 @@
                     } else if (cfg.isBluetooth(iface)) {
                         bluetoothTethered = true;
                     }
-                    activeList.add(iface);
+                    tetherList.add(iface);
                 }
             }
         }
-        Intent broadcast = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
-        broadcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
+        final Intent bcast = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+        bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_AVAILABLE_TETHER,
-                availableList);
-        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, activeList);
-        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER,
-                erroredList);
-        mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL);
+        bcast.putStringArrayListExtra(ConnectivityManager.EXTRA_AVAILABLE_TETHER, availableList);
+        bcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY, localOnlyList);
+        bcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, tetherList);
+        bcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER, erroredList);
+        mContext.sendStickyBroadcastAsUser(bcast, UserHandle.ALL);
         if (DBG) {
             Log.d(TAG, String.format(
-                    "sendTetherStateChangedBroadcast avail=[%s] active=[%s] error=[%s]",
-                    TextUtils.join(",", availableList),
-                    TextUtils.join(",", activeList),
-                    TextUtils.join(",", erroredList)));
+                    "sendTetherStateChangedBroadcast %s=[%s] %s=[%s] %s=[%s] %s=[%s]",
+                    "avail", TextUtils.join(",", availableList),
+                    "local_only", TextUtils.join(",", localOnlyList),
+                    "tether", TextUtils.join(",", tetherList),
+                    "error", TextUtils.join(",", erroredList)));
         }
 
         if (usbTethered) {
@@ -679,7 +726,7 @@
         mLastNotificationId = icon;
 
         notificationManager.notifyAsUser(null, mLastNotificationId,
-                mTetheredNotificationBuilder.build(), UserHandle.ALL);
+                mTetheredNotificationBuilder.buildInto(new Notification()), UserHandle.ALL);
     }
 
     private void clearTetheredNotification() {
@@ -728,7 +775,9 @@
                 mRndisEnabled = rndisEnabled;
                 // start tethering if we have a request pending
                 if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
-                    tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
+                    tetherMatchingInterfaces(
+                            IControlsTethering.STATE_TETHERED,
+                            ConnectivityManager.TETHERING_USB);
                 }
                 mUsbTetherRequested = false;
             }
@@ -743,9 +792,11 @@
                         break;
                     case WifiManager.WIFI_AP_STATE_ENABLED:
                         // When the AP comes up and we've been requested to tether it, do so.
-                        if (mWifiTetherRequested) {
-                            tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
-                        }
+                        // Otherwise, assume it's a local-only hotspot request.
+                        final int state = mWifiTetherRequested
+                                ? IControlsTethering.STATE_TETHERED
+                                : IControlsTethering.STATE_LOCAL_ONLY;
+                        tetherMatchingInterfaces(state, ConnectivityManager.TETHERING_WIFI);
                         break;
                     case WifiManager.WIFI_AP_STATE_DISABLED:
                     case WifiManager.WIFI_AP_STATE_DISABLING:
@@ -775,8 +826,16 @@
         }
     }
 
-    private void tetherMatchingInterfaces(boolean enable, int interfaceType) {
-        if (VDBG) Log.d(TAG, "tetherMatchingInterfaces(" + enable + ", " + interfaceType + ")");
+    // TODO: Consider renaming to something more accurate in its description.
+    // This method:
+    //     - allows requesting either tethering or local hotspot serving states
+    //     - handles both enabling and disabling serving states
+    //     - only tethers the first matching interface in listInterfaces()
+    //       order of a given type
+    private void tetherMatchingInterfaces(int requestedState, int interfaceType) {
+        if (VDBG) {
+            Log.d(TAG, "tetherMatchingInterfaces(" + requestedState + ", " + interfaceType + ")");
+        }
 
         String[] ifaces = null;
         try {
@@ -799,7 +858,20 @@
             return;
         }
 
-        int result = (enable ? tether(chosenIface) : untether(chosenIface));
+        final int result;
+        switch (requestedState) {
+            case IControlsTethering.STATE_UNAVAILABLE:
+            case IControlsTethering.STATE_AVAILABLE:
+                result = untether(chosenIface);
+                break;
+            case IControlsTethering.STATE_TETHERED:
+            case IControlsTethering.STATE_LOCAL_ONLY:
+                result = tether(chosenIface, requestedState);
+                break;
+            default:
+                Log.wtf(TAG, "Unknown interface state: " + requestedState);
+                return;
+        }
         if (result != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
             Log.e(TAG, "unable start or stop tethering on iface " + chosenIface);
             return;
@@ -844,7 +916,8 @@
                 if (mRndisEnabled) {
                     final long ident = Binder.clearCallingIdentity();
                     try {
-                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
+                        tetherMatchingInterfaces(IControlsTethering.STATE_TETHERED,
+                                ConnectivityManager.TETHERING_USB);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
@@ -855,7 +928,8 @@
             } else {
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB);
+                    tetherMatchingInterfaces(IControlsTethering.STATE_AVAILABLE,
+                            ConnectivityManager.TETHERING_USB);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -868,7 +942,7 @@
         return ConnectivityManager.TETHER_ERROR_NO_ERROR;
     }
 
-    // TODO review API - maybe return ArrayList<String> here and below?
+    // TODO review API - figure out how to delete these entirely.
     public String[] getTetheredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
@@ -919,6 +993,14 @@
         }
     }
 
+    private boolean upstreamWanted() {
+        if (!mForwardedDownstreams.isEmpty()) return true;
+
+        synchronized (mPublicSync) {
+            return mUsbTetherRequested || mWifiTetherRequested;
+        }
+    }
+
     // 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.
@@ -933,12 +1015,35 @@
         return false;
     }
 
+    private void reevaluateSimCardProvisioning() {
+        if (!hasMobileHotspotProvisionApp()) return;
+
+        ArrayList<Integer> tethered = new ArrayList<>();
+        synchronized (mPublicSync) {
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                TetherState tetherState = mTetherStates.valueAt(i);
+                if (tetherState.lastState != IControlsTethering.STATE_TETHERED) {
+                    continue;  // Skip interfaces that aren't tethered.
+                }
+                String iface = mTetherStates.keyAt(i);
+                int interfaceType = ifaceNameToType(iface);
+                if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
+                    tethered.add(interfaceType);
+                }
+            }
+        }
+
+        for (int tetherType : tethered) {
+            startProvisionIntent(tetherType);
+        }
+    }
+
     class TetherMasterSM extends StateMachine {
         private static final int BASE_MASTER                    = Protocol.BASE_TETHERING;
-        // an interface SM has requested Tethering
-        static final int CMD_TETHER_MODE_REQUESTED              = BASE_MASTER + 1;
-        // an interface SM has unrequested Tethering
-        static final int CMD_TETHER_MODE_UNREQUESTED            = BASE_MASTER + 2;
+        // an interface SM has requested Tethering/Local Hotspot
+        static final int EVENT_IFACE_SERVING_STATE_ACTIVE       = BASE_MASTER + 1;
+        // an interface SM has unrequested Tethering/Local Hotspot
+        static final int EVENT_IFACE_SERVING_STATE_INACTIVE     = BASE_MASTER + 2;
         // upstream connection change - do the right thing
         static final int CMD_UPSTREAM_CHANGED                   = BASE_MASTER + 3;
         // we don't have a valid upstream conn, check again after a delay
@@ -980,26 +1085,49 @@
 
             //Add states
             mInitialState = new InitialState();
-            addState(mInitialState);
             mTetherModeAliveState = new TetherModeAliveState();
-            addState(mTetherModeAliveState);
-
             mSetIpForwardingEnabledErrorState = new SetIpForwardingEnabledErrorState();
-            addState(mSetIpForwardingEnabledErrorState);
             mSetIpForwardingDisabledErrorState = new SetIpForwardingDisabledErrorState();
-            addState(mSetIpForwardingDisabledErrorState);
             mStartTetheringErrorState = new StartTetheringErrorState();
-            addState(mStartTetheringErrorState);
             mStopTetheringErrorState = new StopTetheringErrorState();
-            addState(mStopTetheringErrorState);
             mSetDnsForwardersErrorState = new SetDnsForwardersErrorState();
+
+            addState(mInitialState);
+            addState(mTetherModeAliveState);
+            addState(mSetIpForwardingEnabledErrorState);
+            addState(mSetIpForwardingDisabledErrorState);
+            addState(mStartTetheringErrorState);
+            addState(mStopTetheringErrorState);
             addState(mSetDnsForwardersErrorState);
 
             mNotifyList = new ArrayList<>();
-            mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList);
+            mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList, mLog);
             setInitialState(mInitialState);
         }
 
+        class InitialState extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                maybeLogMessage(this, message.what);
+                switch (message.what) {
+                    case EVENT_IFACE_SERVING_STATE_ACTIVE:
+                        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;
+                        if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
+                        handleInterfaceServingStateInactive(who);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
         class TetherMasterUtilState extends State {
             @Override
             public boolean processMessage(Message m) {
@@ -1020,20 +1148,25 @@
                 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;
             }
 
@@ -1041,16 +1174,19 @@
                 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;
             }
 
@@ -1174,16 +1310,15 @@
                     // TODO: remove this invocation of NetworkUtils.makeStrings().
                     dnsServers = NetworkUtils.makeStrings(dnses);
                 }
-                if (VDBG) {
-                    Log.d(TAG, "Setting DNS forwarders: Network=" + network +
-                           ", dnsServers=" + Arrays.toString(dnsServers));
-                }
                 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.
-                    Log.e(TAG, "Setting DNS forwarders failed!");
+                    mLog.e("setting DNS forwarders failed, " + e);
                     transitionTo(mSetDnsForwardersErrorState);
                 }
             }
@@ -1204,171 +1339,71 @@
             }
         }
 
-        private class SimChangeListener {
-            private final Context mContext;
-            private final AtomicInteger mSimBcastGenerationNumber;
-            private BroadcastReceiver mBroadcastReceiver;
-
-            SimChangeListener(Context ctx) {
-                mContext = ctx;
-                mSimBcastGenerationNumber = new AtomicInteger(0);
+        private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) {
+            if (mNotifyList.indexOf(who) < 0) {
+                mNotifyList.add(who);
+                mIPv6TetheringCoordinator.addActiveDownstream(who, mode);
             }
 
-            public int generationNumber() {
-                return mSimBcastGenerationNumber.get();
+            if (mode == IControlsTethering.STATE_TETHERED) {
+                mForwardedDownstreams.add(who);
+            } else {
+                mForwardedDownstreams.remove(who);
             }
 
-            public void startListening() {
-                if (DBG) Log.d(TAG, "startListening for SIM changes");
-
-                if (mBroadcastReceiver != null) return;
-
-                mBroadcastReceiver = new SimChangeBroadcastReceiver(
-                        mSimBcastGenerationNumber.incrementAndGet());
-                final IntentFilter filter = new IntentFilter();
-                filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-
-                mContext.registerReceiver(mBroadcastReceiver, filter, null,
-                        mTetherMasterSM.getHandler());
-            }
-
-            public void stopListening() {
-                if (DBG) Log.d(TAG, "stopListening for SIM changes");
-
-                if (mBroadcastReceiver == null) return;
-
-                mSimBcastGenerationNumber.incrementAndGet();
-                mContext.unregisterReceiver(mBroadcastReceiver);
-                mBroadcastReceiver = null;
-            }
-
-            public boolean hasMobileHotspotProvisionApp() {
-                try {
-                    if (!mContext.getResources().getString(com.android.internal.R.string.
-                            config_mobile_hotspot_provision_app_no_ui).isEmpty()) {
-                        Log.d(TAG, "re-evaluate provisioning");
-                        return true;
-                    }
-                } catch (Resources.NotFoundException e) {}
-                Log.d(TAG, "no prov-check needed for new SIM");
-                return false;
-            }
-
-            private boolean isSimCardLoaded(String state) {
-                return IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state);
-            }
-
-            private void startProvisionIntent(int tetherType) {
-                final Intent startProvIntent = new Intent();
-                startProvIntent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
-                startProvIntent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
-                startProvIntent.setComponent(TETHER_SERVICE);
-                mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
-            }
-
-            private class SimChangeBroadcastReceiver extends BroadcastReceiver {
-                // used to verify this receiver is still current
-                final private int mGenerationNumber;
-
-                // used to check the sim state transition from non-loaded to loaded
-                private boolean mSimNotLoadedSeen = false;
-
-                public SimChangeBroadcastReceiver(int generationNumber) {
-                    mGenerationNumber = generationNumber;
-                }
-
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    final int currentGenerationNumber = mSimBcastGenerationNumber.get();
-
-                    if (DBG) {
-                        Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
-                                ", current generationNumber=" + currentGenerationNumber);
-                    }
-                    if (mGenerationNumber != currentGenerationNumber) return;
-
-                    final String state = intent.getStringExtra(
-                            IccCardConstants.INTENT_KEY_ICC_STATE);
-                    Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
-                            mSimNotLoadedSeen);
-
-                    if (!isSimCardLoaded(state)) {
-                        if (!mSimNotLoadedSeen) mSimNotLoadedSeen = true;
-                        return;
-                    }
-
-                    if (isSimCardLoaded(state) && mSimNotLoadedSeen) {
-                        mSimNotLoadedSeen = false;
-
-                        if (!hasMobileHotspotProvisionApp()) return;
-
-                        ArrayList<Integer> tethered = new ArrayList<Integer>();
-                        synchronized (mPublicSync) {
-                            for (int i = 0; i < mTetherStates.size(); i++) {
-                                TetherState tetherState = mTetherStates.valueAt(i);
-                                if (tetherState.lastState != IControlsTethering.STATE_TETHERED) {
-                                    continue;  // Skip interfaces that aren't tethered.
-                                }
-                                String iface = mTetherStates.keyAt(i);
-                                int interfaceType = ifaceNameToType(iface);
-                                if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
-                                    tethered.add(new Integer(interfaceType));
-                                }
-                            }
-                        }
-
-                        for (int tetherType : tethered) {
-                            startProvisionIntent(tetherType);
-                        }
-                    }
+            // If this is a Wi-Fi interface, notify WifiManager of the active serving state.
+            if (who.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
+                final WifiManager mgr = getWifiManager();
+                final String iface = who.interfaceName();
+                switch (mode) {
+                    case IControlsTethering.STATE_TETHERED:
+                        mgr.updateInterfaceIpState(iface, WifiManager.IFACE_IP_MODE_TETHERED);
+                        break;
+                    case IControlsTethering.STATE_LOCAL_ONLY:
+                        mgr.updateInterfaceIpState(iface, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+                        break;
+                    default:
+                        Log.wtf(TAG, "Unknown active serving mode: " + mode);
+                        break;
                 }
             }
         }
 
-        class InitialState extends TetherMasterUtilState {
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                switch (message.what) {
-                    case CMD_TETHER_MODE_REQUESTED:
-                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
-                        if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
-                        if (mNotifyList.indexOf(who) < 0) {
-                            mNotifyList.add(who);
-                            mIPv6TetheringCoordinator.addActiveDownstream(who);
-                        }
-                        transitionTo(mTetherModeAliveState);
-                        break;
-                    case CMD_TETHER_MODE_UNREQUESTED:
-                        who = (TetherInterfaceStateMachine)message.obj;
-                        if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
-                        mNotifyList.remove(who);
-                        mIPv6TetheringCoordinator.removeActiveDownstream(who);
-                        break;
-                    default:
-                        retValue = false;
-                        break;
+        private void handleInterfaceServingStateInactive(TetherInterfaceStateMachine who) {
+            mNotifyList.remove(who);
+            mIPv6TetheringCoordinator.removeActiveDownstream(who);
+            mForwardedDownstreams.remove(who);
+
+            // If this is a Wi-Fi interface, tell WifiManager of any errors.
+            if (who.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
+                if (who.lastError() != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    getWifiManager().updateInterfaceIpState(
+                            who.interfaceName(), WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
                 }
-                return retValue;
             }
         }
 
         class TetherModeAliveState extends TetherMasterUtilState {
-            final SimChangeListener simChange = new SimChangeListener(mContext);
+            boolean mUpstreamWanted = false;
             boolean mTryCell = true;
 
             @Override
             public void enter() {
-                // TODO: examine if we should check the return value.
-                turnOnMasterTetherSettings(); // may transition us out
-                simChange.startListening();
+                // If turning on master tether settings fails, we have already
+                // transitioned to an error state; exit early.
+                if (!turnOnMasterTetherSettings()) {
+                    return;
+                }
+
+                mSimChange.startListening();
                 mUpstreamNetworkMonitor.start();
                 mOffloadController.start();
 
-                // Better try something first pass or crazy tests cases will fail.
-                chooseUpstreamType(true);
-                mTryCell = false;
+                if (upstreamWanted()) {
+                    mUpstreamWanted = true;
+                    chooseUpstreamType(true);
+                    mTryCell = false;
+                }
             }
 
             @Override
@@ -1376,59 +1411,82 @@
                 mOffloadController.stop();
                 unrequestUpstreamMobileConnection();
                 mUpstreamNetworkMonitor.stop();
-                simChange.stopListening();
+                mSimChange.stopListening();
                 notifyTetheredOfNewUpstreamIface(null);
                 handleNewUpstreamNetworkState(null);
             }
 
+            private boolean updateUpstreamWanted() {
+                final boolean previousUpstreamWanted = mUpstreamWanted;
+                mUpstreamWanted = upstreamWanted();
+                return previousUpstreamWanted;
+            }
+
             @Override
             public boolean processMessage(Message message) {
                 maybeLogMessage(this, message.what);
                 boolean retValue = true;
                 switch (message.what) {
-                    case CMD_TETHER_MODE_REQUESTED: {
+                    case EVENT_IFACE_SERVING_STATE_ACTIVE: {
                         TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
-                        if (mNotifyList.indexOf(who) < 0) {
-                            mNotifyList.add(who);
-                            mIPv6TetheringCoordinator.addActiveDownstream(who);
-                        }
+                        handleInterfaceServingStateActive(message.arg1, who);
                         who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
                                 mCurrentUpstreamIface);
+                        // If there has been a change and an upstream is now
+                        // desired, kick off the selection process.
+                        final boolean previousUpstreamWanted = updateUpstreamWanted();
+                        if (!previousUpstreamWanted && mUpstreamWanted) {
+                            chooseUpstreamType(true);
+                        }
                         break;
                     }
-                    case CMD_TETHER_MODE_UNREQUESTED: {
+                    case EVENT_IFACE_SERVING_STATE_INACTIVE: {
                         TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
-                        if (mNotifyList.remove(who)) {
-                            if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who);
-                            if (mNotifyList.isEmpty()) {
-                                turnOffMasterTetherSettings(); // transitions appropriately
-                            } else {
-                                if (DBG) {
-                                    Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() +
-                                            " live requests:");
-                                    for (TetherInterfaceStateMachine o : mNotifyList) {
-                                        Log.d(TAG, "  " + o);
-                                    }
-                                }
-                            }
-                        } else {
-                           Log.e(TAG, "TetherModeAliveState UNREQUESTED has unknown who: " + who);
+                        handleInterfaceServingStateInactive(who);
+
+                        if (mNotifyList.isEmpty()) {
+                            // This transitions us out of TetherModeAliveState,
+                            // either to InitialState or an error state.
+                            turnOffMasterTetherSettings();
+                            break;
                         }
-                        mIPv6TetheringCoordinator.removeActiveDownstream(who);
+
+                        if (DBG) {
+                            Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() +
+                                    " live requests:");
+                            for (TetherInterfaceStateMachine o : mNotifyList) {
+                                Log.d(TAG, "  " + o);
+                            }
+                        }
+                        // If there has been a change and an upstream is no
+                        // longer desired, release any mobile requests.
+                        final boolean previousUpstreamWanted = updateUpstreamWanted();
+                        if (previousUpstreamWanted && !mUpstreamWanted) {
+                            mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
+                        }
                         break;
                     }
                     case CMD_UPSTREAM_CHANGED:
+                        updateUpstreamWanted();
+                        if (!mUpstreamWanted) break;
+
                         // Need to try DUN immediately if Wi-Fi goes down.
                         chooseUpstreamType(true);
                         mTryCell = false;
                         break;
                     case CMD_RETRY_UPSTREAM:
+                        updateUpstreamWanted();
+                        if (!mUpstreamWanted) break;
+
                         chooseUpstreamType(mTryCell);
                         mTryCell = !mTryCell;
                         break;
                     case EVENT_UPSTREAM_CALLBACK: {
+                        updateUpstreamWanted();
+                        if (!mUpstreamWanted) break;
+
                         final NetworkState ns = (NetworkState) message.obj;
 
                         if (ns == null || !pertainsToCurrentUpstream(ns)) {
@@ -1485,12 +1543,13 @@
         }
 
         class ErrorState extends State {
-            int mErrorNotification;
+            private int mErrorNotification;
+
             @Override
             public boolean processMessage(Message message) {
                 boolean retValue = true;
                 switch (message.what) {
-                    case CMD_TETHER_MODE_REQUESTED:
+                    case EVENT_IFACE_SERVING_STATE_ACTIVE:
                         TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         who.sendMessage(mErrorNotification);
                         break;
@@ -1503,6 +1562,7 @@
                 }
                 return retValue;
             }
+
             void notify(int msgType) {
                 mErrorNotification = msgType;
                 for (TetherInterfaceStateMachine sm : mNotifyList) {
@@ -1511,6 +1571,7 @@
             }
 
         }
+
         class SetIpForwardingEnabledErrorState extends ErrorState {
             @Override
             public void enter() {
@@ -1604,22 +1665,43 @@
                     case IControlsTethering.STATE_TETHERED:
                         pw.print("TetheredState");
                         break;
+                    case IControlsTethering.STATE_LOCAL_ONLY:
+                        pw.print("LocalHotspotState");
+                        break;
                     default:
                         pw.print("UnknownState");
                         break;
                 }
                 pw.println(" - lastError = " + tetherState.lastError);
             }
+            pw.println("Upstream wanted: " + upstreamWanted());
             pw.decreaseIndent();
         }
+
+        pw.println("Log:");
+        pw.increaseIndent();
+        if (argsContain(args, SHORT_ARG)) {
+            pw.println("<log removed for brevity>");
+        } else {
+            mLog.dump(fd, pw, args);
+        }
         pw.decreaseIndent();
+
+        pw.decreaseIndent();
+    }
+
+    private static boolean argsContain(String[] args, String target) {
+        for (String arg : args) {
+            if (arg.equals(target)) return true;
+        }
+        return false;
     }
 
     @Override
     public void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
                                            int state, int error) {
         synchronized (mPublicSync) {
-            TetherState tetherState = mTetherStates.get(iface);
+            final TetherState tetherState = mTetherStates.get(iface);
             if (tetherState != null && tetherState.stateMachine.equals(who)) {
                 tetherState.lastState = state;
                 tetherState.lastError = error;
@@ -1628,10 +1710,7 @@
             }
         }
 
-        if (DBG) {
-            Log.d(TAG, "iface " + iface + " notified that it was in state " + state +
-                    " with error " + error);
-        }
+        mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error));
 
         try {
             // Notify that we're tethering (or not) this interface.
@@ -1648,27 +1727,58 @@
         if (error == ConnectivityManager.TETHER_ERROR_MASTER_ERROR) {
             mTetherMasterSM.sendMessage(TetherMasterSM.CMD_CLEAR_ERROR, who);
         }
+        int which;
         switch (state) {
             case IControlsTethering.STATE_UNAVAILABLE:
             case IControlsTethering.STATE_AVAILABLE:
-                mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who);
+                which = TetherMasterSM.EVENT_IFACE_SERVING_STATE_INACTIVE;
                 break;
             case IControlsTethering.STATE_TETHERED:
-                mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED, who);
+            case IControlsTethering.STATE_LOCAL_ONLY:
+                which = TetherMasterSM.EVENT_IFACE_SERVING_STATE_ACTIVE;
                 break;
+            default:
+                Log.wtf(TAG, "Unknown interface state: " + state);
+                return;
         }
+        mTetherMasterSM.sendMessage(which, state, 0, who);
         sendTetherStateChangedBroadcast();
     }
 
-    private void trackNewTetherableInterface(String iface, int interfaceType) {
-        TetherState tetherState;
-        tetherState = new TetherState(new TetherInterfaceStateMachine(iface, mLooper,
-                interfaceType, mNMService, mStatsService, this,
-                new IPv6TetheringInterfaceServices(iface, mNMService)));
+    private void maybeTrackNewInterfaceLocked(final String iface) {
+        // If we don't care about this type of interface, ignore.
+        final int interfaceType = ifaceNameToType(iface);
+        if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
+            mLog.log(iface + " is not a tetherable iface, ignoring");
+            return;
+        }
+
+        // If we have already started a TISM for this interface, skip.
+        if (mTetherStates.containsKey(iface)) {
+            mLog.log("active iface (" + iface + ") reported as added, ignoring");
+            return;
+        }
+
+        mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
+        final TetherState tetherState = new TetherState(
+                new TetherInterfaceStateMachine(
+                    iface, mLooper, interfaceType, mLog, mNMService, mStatsService, this,
+                    new IPv6TetheringInterfaceServices(iface, mNMService, mLog)));
         mTetherStates.put(iface, tetherState);
         tetherState.stateMachine.start();
     }
 
+    private void stopTrackingInterfaceLocked(final String iface) {
+        final TetherState tetherState = mTetherStates.get(iface);
+        if (tetherState == null) {
+            mLog.log("attempting to remove unknown iface (" + iface + "), ignoring");
+            return;
+        }
+        tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+        mLog.log("removing TetheringInterfaceStateMachine for: " + iface);
+        mTetherStates.remove(iface);
+    }
+
     private static String[] copy(String[] strarray) {
         return Arrays.copyOf(strarray, strarray.length);
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 584994a..5dc8640 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -264,6 +264,30 @@
     }
 
     /**
+     * Chooses whether to force all connections to go though VPN.
+     *
+     * Used to enable/disable legacy VPN lockdown.
+     *
+     * This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)};
+     * previous settings from calling that function will be replaced and saved with the
+     * always-on state.
+     *
+     * @param lockdown whether to prevent all traffic outside of a VPN.
+     */
+    public synchronized void setLockdown(boolean lockdown) {
+        enforceControlPermissionOrInternalCaller();
+
+        setVpnForcedLocked(lockdown);
+        mLockdown = lockdown;
+
+        // Update app lockdown setting if it changed. Legacy VPN lockdown status is controlled by
+        // LockdownVpnTracker.isEnabled() which keeps track of its own state.
+        if (mAlwaysOn) {
+            saveAlwaysOnPackage();
+        }
+    }
+
+    /**
      * Configures an always-on VPN connection through a specific application.
      * This connection is automatically granted and persisted after a reboot.
      *
@@ -376,7 +400,7 @@
             mSystemServices.settingsSecurePutStringForUser(Settings.Secure.ALWAYS_ON_VPN_APP,
                     getAlwaysOnPackage(), mUserHandle);
             mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
-                    (mLockdown ? 1 : 0), mUserHandle);
+                    (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -557,6 +581,7 @@
             mConfig = null;
 
             updateState(DetailedState.IDLE, "prepare");
+            setVpnForcedLocked(mLockdown);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -1003,9 +1028,7 @@
                         Log.wtf(TAG, "Failed to add restricted user to owner", e);
                     }
                 }
-                if (mAlwaysOn) {
-                    setVpnForcedLocked(mLockdown);
-                }
+                setVpnForcedLocked(mLockdown);
             }
         }
     }
@@ -1022,9 +1045,7 @@
                         Log.wtf(TAG, "Failed to remove restricted user to owner", e);
                     }
                 }
-                if (mAlwaysOn) {
-                    setVpnForcedLocked(mLockdown);
-                }
+                setVpnForcedLocked(mLockdown);
             }
         }
     }
@@ -1034,7 +1055,7 @@
      */
     public synchronized void onUserStopped() {
         // Switch off networking lockdown (if it was enabled)
-        setVpnForcedLocked(false);
+        setLockdown(false);
         mAlwaysOn = false;
 
         unregisterPackageChangeReceiverLocked();
@@ -1061,20 +1082,31 @@
      */
     @GuardedBy("this")
     private void setVpnForcedLocked(boolean enforce) {
+        final List<String> exemptedPackages =
+                isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage);
+        setVpnForcedWithExemptionsLocked(enforce, exemptedPackages);
+    }
+
+    /**
+     * @see #setVpnForcedLocked
+     */
+    @GuardedBy("this")
+    private void setVpnForcedWithExemptionsLocked(boolean enforce,
+            @Nullable List<String> exemptedPackages) {
         final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
+
+        Set<UidRange> addedRanges = Collections.emptySet();
         if (enforce) {
-            final Set<UidRange> addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
+            addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
                     /* allowedApplications */ null,
-                    /* disallowedApplications */ Collections.singletonList(mPackage));
+                    /* disallowedApplications */ exemptedPackages);
 
             removedRanges.removeAll(addedRanges);
             addedRanges.removeAll(mBlockedUsers);
-
-            setAllowOnlyVpnForUids(false, removedRanges);
-            setAllowOnlyVpnForUids(true, addedRanges);
-        } else {
-            setAllowOnlyVpnForUids(false, removedRanges);
         }
+
+        setAllowOnlyVpnForUids(false, removedRanges);
+        setAllowOnlyVpnForUids(true, addedRanges);
     }
 
     /**
diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
index 449b8a8..c5c86bd 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
@@ -23,8 +23,9 @@
  */
 public interface IControlsTethering {
     public final int STATE_UNAVAILABLE = 0;
-    public final int STATE_AVAILABLE = 1;
-    public final int STATE_TETHERED = 2;
+    public final int STATE_AVAILABLE   = 1;
+    public final int STATE_TETHERED    = 2;
+    public final int STATE_LOCAL_ONLY  = 3;
 
     /**
      * Notify that |who| has changed its tethering state.  This may be called from any thread.
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
index 9173feb..518f6c1 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -24,12 +24,18 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkState;
 import android.net.RouteInfo;
+import android.net.util.NetworkConstants;
+import android.net.util.SharedLog;
 import android.util.Log;
 
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.Random;
 
 
 /**
@@ -45,36 +51,75 @@
     private static final boolean DBG = false;
     private static final boolean VDBG = false;
 
-    private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
-    private final LinkedList<TetherInterfaceStateMachine> mActiveDownstreams;
-    private NetworkState mUpstreamNetworkState;
+    private static class Downstream {
+        public final TetherInterfaceStateMachine tism;
+        public final int mode;  // IControlsTethering.STATE_*
+        // Used to append to a ULA /48, constructing a ULA /64 for local use.
+        public final short subnetId;
 
-    public IPv6TetheringCoordinator(ArrayList<TetherInterfaceStateMachine> notifyList) {
-        mNotifyList = notifyList;
-        mActiveDownstreams = new LinkedList<>();
+        Downstream(TetherInterfaceStateMachine tism, int mode, short subnetId) {
+            this.tism = tism;
+            this.mode = mode;
+            this.subnetId = subnetId;
+        }
     }
 
-    public void addActiveDownstream(TetherInterfaceStateMachine downstream) {
-        if (mActiveDownstreams.indexOf(downstream) == -1) {
+    private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
+    private final SharedLog mLog;
+    // NOTE: mActiveDownstreams is a list and not a hash data structure because
+    // we keep active downstreams in arrival order.  This is done so /64s can
+    // be parceled out on a "first come, first served" basis and a /64 used by
+    // a downstream that is no longer active can be redistributed to any next
+    // waiting active downstream (again, in arrival order).
+    private final LinkedList<Downstream> mActiveDownstreams;
+    private final byte[] mUniqueLocalPrefix;
+    private short mNextSubnetId;
+    private NetworkState mUpstreamNetworkState;
+
+    public IPv6TetheringCoordinator(ArrayList<TetherInterfaceStateMachine> notifyList,
+                                    SharedLog log) {
+        mNotifyList = notifyList;
+        mLog = log.forSubComponent(TAG);
+        mActiveDownstreams = new LinkedList<>();
+        mUniqueLocalPrefix = generateUniqueLocalPrefix();
+        mNextSubnetId = 0;
+    }
+
+    public void addActiveDownstream(TetherInterfaceStateMachine downstream, int mode) {
+        if (findDownstream(downstream) == null) {
             // Adding a new downstream appends it to the list. Adding a
             // downstream a second time without first removing it has no effect.
-            mActiveDownstreams.offer(downstream);
+            // We never change the mode of a downstream except by first removing
+            // it and then re-adding it (with its new mode specified);
+            if (mActiveDownstreams.offer(new Downstream(downstream, mode, mNextSubnetId))) {
+                // Make sure subnet IDs are always positive. They are appended
+                // to a ULA /48 to make a ULA /64 for local use.
+                mNextSubnetId = (short) Math.max(0, mNextSubnetId + 1);
+            }
             updateIPv6TetheringInterfaces();
         }
     }
 
     public void removeActiveDownstream(TetherInterfaceStateMachine downstream) {
         stopIPv6TetheringOn(downstream);
-        if (mActiveDownstreams.remove(downstream)) {
+        if (mActiveDownstreams.remove(findDownstream(downstream))) {
             updateIPv6TetheringInterfaces();
         }
+
+        // When tethering is stopping we can reset the subnet counter.
+        if (mNotifyList.isEmpty()) {
+            if (!mActiveDownstreams.isEmpty()) {
+                Log.wtf(TAG, "Tethering notify list empty, IPv6 downstreams non-empty.");
+            }
+            mNextSubnetId = 0;
+        }
     }
 
     public void updateUpstreamNetworkState(NetworkState ns) {
         if (VDBG) {
             Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns));
         }
-        if (!canTetherIPv6(ns)) {
+        if (!canTetherIPv6(ns, mLog)) {
             stopIPv6TetheringOnAllInterfaces();
             setUpstreamNetworkState(null);
             return;
@@ -109,9 +154,7 @@
                     null);
         }
 
-        if (DBG) {
-            Log.d(TAG, "setUpstreamNetworkState: " + toDebugString(mUpstreamNetworkState));
-        }
+        mLog.log("setUpstreamNetworkState: " + toDebugString(mUpstreamNetworkState));
     }
 
     private void updateIPv6TetheringInterfaces() {
@@ -123,20 +166,31 @@
     }
 
     private LinkProperties getInterfaceIPv6LinkProperties(TetherInterfaceStateMachine sm) {
-        if (mUpstreamNetworkState == null) return null;
-
         if (sm.interfaceType() == ConnectivityManager.TETHERING_BLUETOOTH) {
             // TODO: Figure out IPv6 support on PAN interfaces.
             return null;
         }
 
+        final Downstream ds = findDownstream(sm);
+        if (ds == null) return null;
+
+        if (ds.mode == IControlsTethering.STATE_LOCAL_ONLY) {
+            // Build a Unique Locally-assigned Prefix configuration.
+            return getUniqueLocalConfig(mUniqueLocalPrefix, ds.subnetId);
+        }
+
+        // This downstream is in IControlsTethering.STATE_TETHERED mode.
+        if (mUpstreamNetworkState == null || mUpstreamNetworkState.linkProperties == null) {
+            return null;
+        }
+
         // NOTE: Here, in future, we would have policies to decide how to divvy
         // up the available dedicated prefixes among downstream interfaces.
         // At this time we have no such mechanism--we only support tethering
         // IPv6 toward the oldest (first requested) active downstream.
 
-        final TetherInterfaceStateMachine currentActive = mActiveDownstreams.peek();
-        if (currentActive != null && currentActive == sm) {
+        final Downstream currentActive = mActiveDownstreams.peek();
+        if (currentActive != null && currentActive.tism == sm) {
             final LinkProperties lp = getIPv6OnlyLinkProperties(
                     mUpstreamNetworkState.linkProperties);
             if (lp.hasIPv6DefaultRoute() && lp.hasGlobalIPv6Address()) {
@@ -147,7 +201,14 @@
         return null;
     }
 
-    private static boolean canTetherIPv6(NetworkState ns) {
+    Downstream findDownstream(TetherInterfaceStateMachine tism) {
+        for (Downstream ds : mActiveDownstreams) {
+            if (ds.tism == tism) return ds;
+        }
+        return null;
+    }
+
+    private static boolean canTetherIPv6(NetworkState ns, SharedLog sharedLog) {
         // Broadly speaking:
         //
         //     [1] does the upstream have an IPv6 default route?
@@ -201,13 +262,11 @@
 
         final boolean outcome = canTether && supportedConfiguration;
 
-        if (VDBG) {
-            if (ns == null) {
-                Log.d(TAG, "No available upstream.");
-            } else {
-                Log.d(TAG, String.format("IPv6 tethering is %s for upstream: %s",
-                        (outcome ? "available" : "not available"), toDebugString(ns)));
-            }
+        if (ns == null) {
+            sharedLog.log("No available upstream.");
+        } else {
+            sharedLog.log(String.format("IPv6 tethering is %s for upstream: %s",
+                    (outcome ? "available" : "not available"), toDebugString(ns)));
         }
 
         return outcome;
@@ -263,6 +322,44 @@
                !ip.isMulticastAddress();
     }
 
+    private static LinkProperties getUniqueLocalConfig(byte[] ulp, short subnetId) {
+        final LinkProperties lp = new LinkProperties();
+
+        final IpPrefix local48 = makeUniqueLocalPrefix(ulp, (short) 0, 48);
+        lp.addRoute(new RouteInfo(local48, null, null));
+
+        final IpPrefix local64 = makeUniqueLocalPrefix(ulp, subnetId, 64);
+        // Because this is a locally-generated ULA, we don't have an upstream
+        // address. But because the downstream IP address management code gets
+        // its prefix from the upstream's IP address, we create a fake one here.
+        lp.addLinkAddress(new LinkAddress(local64.getAddress(), 64));
+
+        lp.setMtu(NetworkConstants.ETHER_MTU);
+        return lp;
+    }
+
+    private static IpPrefix makeUniqueLocalPrefix(byte[] in6addr, short subnetId, int prefixlen) {
+        final byte[] bytes = Arrays.copyOf(in6addr, in6addr.length);
+        bytes[7] = (byte) (subnetId >> 8);
+        bytes[8] = (byte) subnetId;
+        return new IpPrefix(bytes, prefixlen);
+    }
+
+    // Generates a Unique Locally-assigned Prefix:
+    //
+    //     https://tools.ietf.org/html/rfc4193#section-3.1
+    //
+    // The result is a /48 that can be used for local-only communications.
+    private static byte[] generateUniqueLocalPrefix() {
+        final byte[] ulp = new byte[6];  // 6 = 48bits / 8bits/byte
+        (new Random()).nextBytes(ulp);
+
+        final byte[] in6addr = Arrays.copyOf(ulp, NetworkConstants.IPV6_ADDR_LEN);
+        in6addr[0] = (byte) 0xfd;  // fc00::/7 and L=1
+
+        return in6addr;
+    }
+
     private static String toDebugString(NetworkState ns) {
         if (ns == null) {
             return "NetworkState{null}";
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
index 8c6430c..adf4af8 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
@@ -28,10 +28,10 @@
 import android.net.ip.RouterAdvertisementDaemon;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
 import android.net.util.NetdService;
+import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
 import android.os.ServiceSpecificException;
 import android.os.RemoteException;
-import android.util.Log;
 import android.util.Slog;
 
 import java.net.Inet6Address;
@@ -42,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Objects;
+import java.util.Random;
 
 
 /**
@@ -53,6 +54,7 @@
 
     private final String mIfName;
     private final INetworkManagementService mNMService;
+    private final SharedLog mLog;
 
     private NetworkInterface mNetworkInterface;
     private byte[] mHwAddr;
@@ -60,16 +62,25 @@
     private RouterAdvertisementDaemon mRaDaemon;
     private RaParams mLastRaParams;
 
-    public IPv6TetheringInterfaceServices(String ifname, INetworkManagementService nms) {
+    public IPv6TetheringInterfaceServices(
+            String ifname, INetworkManagementService nms, SharedLog log) {
         mIfName = ifname;
         mNMService = nms;
+        mLog = log.forSubComponent(mIfName);
     }
 
     public boolean start() {
+        // TODO: Refactor for testability (perhaps passing an android.system.Os
+        // instance and calling getifaddrs() directly).
         try {
             mNetworkInterface = NetworkInterface.getByName(mIfName);
         } catch (SocketException e) {
-            Log.e(TAG, "Failed to find NetworkInterface for " + mIfName, e);
+            mLog.e("Error looking up NetworkInterfaces: " + e);
+            stop();
+            return false;
+        }
+        if (mNetworkInterface == null) {
+            mLog.e("Failed to find NetworkInterface");
             stop();
             return false;
         }
@@ -77,7 +88,7 @@
         try {
             mHwAddr = mNetworkInterface.getHardwareAddress();
         } catch (SocketException e) {
-            Log.e(TAG, "Failed to find hardware address for " + mIfName, e);
+            mLog.e("Failed to find hardware address: " + e);
             stop();
             return false;
         }
@@ -153,11 +164,11 @@
             try {
                 final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
                 if (removalFailures > 0) {
-                    Log.e(TAG, String.format("Failed to remove %d IPv6 routes from local table.",
+                    mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
                             removalFailures));
                 }
             } catch (RemoteException e) {
-                Log.e(TAG, "Failed to remove IPv6 routes from local table: ", e);
+                mLog.e("Failed to remove IPv6 routes from local table: " + e);
             }
         }
 
@@ -187,7 +198,7 @@
                     // error (EEXIST is silently ignored).
                     mNMService.addInterfaceToLocalNetwork(mIfName, toBeAdded);
                 } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to add IPv6 routes to local table: ", e);
+                    mLog.e("Failed to add IPv6 routes to local table: " + e);
                 }
             }
         }
@@ -198,7 +209,7 @@
         final INetd netd = NetdService.getInstance();
         if (netd == null) {
             if (newDnses != null) newDnses.clear();
-            Log.e(TAG, "No netd service instance available; not setting local IPv6 addresses");
+            mLog.e("No netd service instance available; not setting local IPv6 addresses");
             return;
         }
 
@@ -209,7 +220,7 @@
                 try {
                     netd.interfaceDelAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH);
                 } catch (ServiceSpecificException | RemoteException e) {
-                    Log.e(TAG, "Failed to remove local dns IP: " + dnsString, e);
+                    mLog.e("Failed to remove local dns IP " + dnsString + ": " + e);
                 }
             }
         }
@@ -226,7 +237,7 @@
                 try {
                     netd.interfaceAddAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH);
                 } catch (ServiceSpecificException | RemoteException e) {
-                    Log.e(TAG, "Failed to add local dns IP: " + dnsString, e);
+                    mLog.e("Failed to add local dns IP " + dnsString + ": " + e);
                     newDnses.remove(dns);
                 }
             }
@@ -235,7 +246,7 @@
         try {
             netd.tetherApplyDnsInterfaces();
         } catch (ServiceSpecificException | RemoteException e) {
-            Log.e(TAG, "Failed to update local DNS caching server");
+            mLog.e("Failed to update local DNS caching server");
             if (newDnses != null) newDnses.clear();
         }
     }
@@ -267,10 +278,10 @@
         return localRoutes;
     }
 
-    // Given a prefix like 2001:db8::/64 return 2001:db8::1.
+    // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1.
     private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) {
         final byte[] dnsBytes = localPrefix.getRawAddress();
-        dnsBytes[dnsBytes.length - 1] = 0x1;
+        dnsBytes[dnsBytes.length - 1] = getRandomNonZeroByte();
         try {
             return Inet6Address.getByAddress(null, dnsBytes, 0);
         } catch (UnknownHostException e) {
@@ -278,4 +289,11 @@
             return null;
         }
     }
+
+    private static byte getRandomNonZeroByte() {
+        final byte random = (byte) (new Random()).nextInt();
+        // Don't pick the subnet-router anycast address, since that might be
+        // in use on the upstream already.
+        return (random != 0) ? random : 0x1;
+    }
 }
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 220e751..ec7ab5b 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -18,10 +18,11 @@
 
 import android.net.LinkProperties;
 import android.os.Handler;
-import android.util.Log;
+import android.net.util.SharedLog;
 
 /**
- * A wrapper around hardware offload interface.
+ * A class to encapsulate the business logic of programming the tethering
+ * hardware offload interface.
  *
  * @hide
  */
@@ -29,25 +30,50 @@
     private static final String TAG = OffloadController.class.getSimpleName();
 
     private final Handler mHandler;
+    private final OffloadHardwareInterface mHwInterface;
+    private final SharedLog mLog;
+    private boolean mConfigInitialized;
+    private boolean mControlInitialized;
     private LinkProperties mUpstreamLinkProperties;
 
-    public OffloadController(Handler h) {
+    public OffloadController(Handler h, OffloadHardwareInterface hwi, SharedLog log) {
         mHandler = h;
+        mHwInterface = hwi;
+        mLog = log.forSubComponent(TAG);
     }
 
     public void start() {
-        // TODO: initOffload() and configure callbacks to be handled on our
-        // preferred Handler.
-        Log.d(TAG, "tethering offload not supported");
+        if (started()) return;
+
+        if (!mConfigInitialized) {
+            mConfigInitialized = mHwInterface.initOffloadConfig();
+            if (!mConfigInitialized) {
+                mLog.i("tethering offload config not supported");
+                return;
+            }
+        }
+
+        // TODO: Create and register ITetheringOffloadCallback.
+        mControlInitialized = mHwInterface.initOffloadControl();
     }
 
     public void stop() {
-        // TODO: stopOffload().
         mUpstreamLinkProperties = null;
+        mHwInterface.stopOffloadControl();
+        mControlInitialized = false;
+        mConfigInitialized = false;
     }
 
     public void setUpstreamLinkProperties(LinkProperties lp) {
+        if (!started()) return;
+
         // TODO: setUpstreamParameters().
         mUpstreamLinkProperties = lp;
     }
+
+    // TODO: public void addDownStream(...)
+
+    private boolean started() {
+        return mConfigInitialized && mControlInitialized;
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
new file mode 100644
index 0000000..87fc491
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl.stopOffloadCallback;
+import android.os.RemoteException;
+import android.util.Log;
+
+
+/**
+ * Capture tethering dependencies, for injection.
+ *
+ * @hide
+ */
+public class OffloadHardwareInterface {
+    private static final String TAG = OffloadHardwareInterface.class.getSimpleName();
+
+    private static native boolean configOffload();
+
+    private IOffloadControl mOffloadControl;
+
+    public OffloadHardwareInterface() {}
+
+    public boolean initOffloadConfig() {
+        return configOffload();
+    }
+
+    // TODO: Extend this to take a TetheringControlCallback for registration.
+    public boolean initOffloadControl() {
+        if (mOffloadControl == null) {
+            try {
+                mOffloadControl = IOffloadControl.getService();
+            } catch (RemoteException e) {
+                Log.d(TAG, "tethering offload control not supported: " + e);
+                return false;
+            }
+        }
+
+        // TODO: call mOffloadControl.initOffload(...callback...);
+
+        return true;
+    }
+
+    public void stopOffloadControl() {
+        if (mOffloadControl == null) return;
+
+        try {
+            final stopOffloadCallback cb = new stopOffloadCallback() {
+                @Override
+                public void onValues(boolean success, String errMsg) {
+                    if (success) return;
+
+                    Log.e(TAG, "stopOffload failed: " + errMsg);
+                }
+            };
+            mOffloadControl.stopOffload(cb);
+        } catch (RemoteException e) {
+            Log.d(TAG, "failed to stopOffload: " + e);
+        }
+        mOffloadControl = null;
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
new file mode 100644
index 0000000..3e60f9f
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static com.android.internal.telephony.IccCardConstants.INTENT_VALUE_ICC_LOADED;
+import static com.android.internal.telephony.IccCardConstants.INTENT_KEY_ICC_STATE;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * A utility class that runs the provided callback on the provided handler when
+ * observing a new SIM card having been loaded.
+ *
+ * @hide
+ */
+public class SimChangeListener {
+    private static final String TAG = SimChangeListener.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private final Context mContext;
+    private final Handler mTarget;
+    private final AtomicInteger mSimBcastGenerationNumber;
+    private final Runnable mCallback;
+    private BroadcastReceiver mBroadcastReceiver;
+
+    public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) {
+        mContext = ctx;
+        mTarget = handler;
+        mCallback = onSimCardLoadedCallback;
+        mSimBcastGenerationNumber = new AtomicInteger(0);
+    }
+
+    public int generationNumber() {
+        return mSimBcastGenerationNumber.get();
+    }
+
+    public void startListening() {
+        if (DBG) Log.d(TAG, "startListening for SIM changes");
+
+        if (mBroadcastReceiver != null) return;
+
+        mBroadcastReceiver = new SimChangeBroadcastReceiver(
+                mSimBcastGenerationNumber.incrementAndGet());
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+
+        mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget);
+    }
+
+    public void stopListening() {
+        if (DBG) Log.d(TAG, "stopListening for SIM changes");
+
+        if (mBroadcastReceiver == null) return;
+
+        mSimBcastGenerationNumber.incrementAndGet();
+        mContext.unregisterReceiver(mBroadcastReceiver);
+        mBroadcastReceiver = null;
+    }
+
+    private boolean isSimCardLoaded(String state) {
+        return INTENT_VALUE_ICC_LOADED.equals(state);
+    }
+
+    private class SimChangeBroadcastReceiver extends BroadcastReceiver {
+        // used to verify this receiver is still current
+        final private int mGenerationNumber;
+
+        // used to check the sim state transition from non-loaded to loaded
+        private boolean mSimNotLoadedSeen = false;
+
+        public SimChangeBroadcastReceiver(int generationNumber) {
+            mGenerationNumber = generationNumber;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int currentGenerationNumber = mSimBcastGenerationNumber.get();
+
+            if (DBG) {
+                Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
+                        ", current generationNumber=" + currentGenerationNumber);
+            }
+            if (mGenerationNumber != currentGenerationNumber) return;
+
+            final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
+            Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
+                    mSimNotLoadedSeen);
+
+            if (!isSimCardLoaded(state)) {
+                mSimNotLoadedSeen = true;
+                return;
+            }
+
+            if (mSimNotLoadedSeen) {
+                mSimNotLoadedSeen = false;
+                mCallback.run();
+            }
+        }
+    }
+}
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 710ab33..4a1d405 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -22,6 +22,7 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
+import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
@@ -78,9 +79,11 @@
     public static final int CMD_IPV6_TETHER_UPDATE          = BASE_IFACE + 13;
 
     private final State mInitialState;
+    private final State mLocalHotspotState;
     private final State mTetheredState;
     private final State mUnavailableState;
 
+    private final SharedLog mLog;
     private final INetworkManagementService mNMService;
     private final INetworkStatsService mStatsService;
     private final IControlsTethering mTetherController;
@@ -92,10 +95,12 @@
     private int mLastError;
     private String mMyUpstreamIfaceName;  // may change over time
 
-    public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType,
-                    INetworkManagementService nMService, INetworkStatsService statsService,
-                    IControlsTethering tetherController, IPv6TetheringInterfaceServices ipv6Svc) {
+    public TetherInterfaceStateMachine(
+            String ifaceName, Looper looper, int interfaceType, SharedLog log,
+            INetworkManagementService nMService, INetworkStatsService statsService,
+            IControlsTethering tetherController, IPv6TetheringInterfaceServices ipv6Svc) {
         super(ifaceName, looper);
+        mLog = log.forSubComponent(ifaceName);
         mNMService = nMService;
         mStatsService = statsService;
         mTetherController = tetherController;
@@ -105,18 +110,22 @@
         mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
 
         mInitialState = new InitialState();
-        addState(mInitialState);
+        mLocalHotspotState = new LocalHotspotState();
         mTetheredState = new TetheredState();
-        addState(mTetheredState);
         mUnavailableState = new UnavailableState();
+        addState(mInitialState);
+        addState(mLocalHotspotState);
+        addState(mTetheredState);
         addState(mUnavailableState);
 
         setInitialState(mInitialState);
     }
 
-    public int interfaceType() {
-        return mInterfaceType;
-    }
+    public String interfaceName() { return mIfaceName; }
+
+    public int interfaceType() { return mInterfaceType; }
+
+    public int lastError() { return mLastError; }
 
     // configured when we start tethering and unconfig'd on error or conclusion
     private boolean configureIfaceIp(boolean enabled) {
@@ -157,7 +166,7 @@
                 mNMService.setInterfaceConfig(mIfaceName, ifcg);
             }
         } catch (Exception e) {
-            Log.e(TAG, "Error configuring interface " + mIfaceName, e);
+            mLog.e("Error configuring interface " + e);
             return false;
         }
 
@@ -172,12 +181,15 @@
         }
     }
 
+    private void sendInterfaceState(int newInterfaceState) {
+        mTetherController.notifyInterfaceStateChange(
+                mIfaceName, TetherInterfaceStateMachine.this, newInterfaceState, mLastError);
+    }
+
     class InitialState extends State {
         @Override
         public void enter() {
-            mTetherController.notifyInterfaceStateChange(
-                    mIfaceName, TetherInterfaceStateMachine.this,
-                    IControlsTethering.STATE_AVAILABLE, mLastError);
+            sendInterfaceState(IControlsTethering.STATE_AVAILABLE);
         }
 
         @Override
@@ -187,7 +199,16 @@
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
                     mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
-                    transitionTo(mTetheredState);
+                    switch (message.arg1) {
+                        case IControlsTethering.STATE_LOCAL_ONLY:
+                            transitionTo(mLocalHotspotState);
+                            break;
+                        case IControlsTethering.STATE_TETHERED:
+                            transitionTo(mTetheredState);
+                            break;
+                        default:
+                            mLog.e("Invalid tethering interface serving state specified.");
+                    }
                     break;
                 case CMD_INTERFACE_DOWN:
                     transitionTo(mUnavailableState);
@@ -204,32 +225,27 @@
         }
     }
 
-    class TetheredState extends State {
+    class BaseServingState extends State {
         @Override
         public void enter() {
             if (!configureIfaceIp(true)) {
                 mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
-                transitionTo(mInitialState);
                 return;
             }
 
             try {
                 mNMService.tetherInterface(mIfaceName);
             } catch (Exception e) {
-                Log.e(TAG, "Error Tethering: " + e.toString());
+                mLog.e("Error Tethering: " + e);
                 mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
-                transitionTo(mInitialState);
                 return;
             }
 
             if (!mIPv6TetherSvc.start()) {
-                Log.e(TAG, "Failed to start IPv6TetheringInterfaceServices");
+                mLog.e("Failed to start IPv6TetheringInterfaceServices");
+                // TODO: Make this a fatal error once Bluetooth IPv6 is sorted.
+                return;
             }
-
-            if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
-            mTetherController.notifyInterfaceStateChange(
-                    mIfaceName, TetherInterfaceStateMachine.this,
-                    IControlsTethering.STATE_TETHERED, mLastError);
         }
 
         @Override
@@ -238,18 +254,107 @@
             // of these operations, but it doesn't really change that we have to try them
             // all in sequence.
             mIPv6TetherSvc.stop();
-            cleanupUpstream();
 
             try {
                 mNMService.untetherInterface(mIfaceName);
-            } catch (Exception ee) {
+            } catch (Exception e) {
                 mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
-                Log.e(TAG, "Failed to untether interface: " + ee.toString());
+                mLog.e("Failed to untether interface: " + e);
             }
 
             configureIfaceIp(false);
         }
 
+        @Override
+        public boolean processMessage(Message message) {
+            maybeLogMessage(this, message.what);
+            switch (message.what) {
+                case CMD_TETHER_UNREQUESTED:
+                    transitionTo(mInitialState);
+                    if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName);
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    transitionTo(mUnavailableState);
+                    if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName);
+                    break;
+                case CMD_IPV6_TETHER_UPDATE:
+                    mIPv6TetherSvc.updateUpstreamIPv6LinkProperties(
+                            (LinkProperties) message.obj);
+                    break;
+                case CMD_IP_FORWARDING_ENABLE_ERROR:
+                case CMD_IP_FORWARDING_DISABLE_ERROR:
+                case CMD_START_TETHERING_ERROR:
+                case CMD_STOP_TETHERING_ERROR:
+                case CMD_SET_DNS_FORWARDERS_ERROR:
+                    mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+                    transitionTo(mInitialState);
+                    break;
+                default:
+                    return false;
+            }
+            return true;
+        }
+    }
+
+    // Handling errors in BaseServingState.enter() by transitioning is
+    // problematic because transitioning during a multi-state jump yields
+    // a Log.wtf(). Ultimately, there should be only one ServingState,
+    // and forwarding and NAT rules should be handled by a coordinating
+    // functional element outside of TetherInterfaceStateMachine.
+    class LocalHotspotState extends BaseServingState {
+        @Override
+        public void enter() {
+            super.enter();
+            if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                transitionTo(mInitialState);
+            }
+
+            if (DBG) Log.d(TAG, "Local hotspot " + mIfaceName);
+            sendInterfaceState(IControlsTethering.STATE_LOCAL_ONLY);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (super.processMessage(message)) return true;
+
+            maybeLogMessage(this, message.what);
+            switch (message.what) {
+                case CMD_TETHER_REQUESTED:
+                    mLog.e("CMD_TETHER_REQUESTED while in local-only hotspot mode.");
+                    break;
+                case CMD_TETHER_CONNECTION_CHANGED:
+                    // Ignored in local hotspot state.
+                    break;
+                default:
+                    return false;
+            }
+            return true;
+        }
+    }
+
+    // Handling errors in BaseServingState.enter() by transitioning is
+    // problematic because transitioning during a multi-state jump yields
+    // a Log.wtf(). Ultimately, there should be only one ServingState,
+    // and forwarding and NAT rules should be handled by a coordinating
+    // functional element outside of TetherInterfaceStateMachine.
+    class TetheredState extends BaseServingState {
+        @Override
+        public void enter() {
+            super.enter();
+            if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                transitionTo(mInitialState);
+            }
+
+            if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
+            sendInterfaceState(IControlsTethering.STATE_TETHERED);
+        }
+
+        @Override
+        public void exit() {
+            cleanupUpstream();
+            super.exit();
+        }
+
         private void cleanupUpstream() {
             if (mMyUpstreamIfaceName == null) return;
 
@@ -282,16 +387,13 @@
 
         @Override
         public boolean processMessage(Message message) {
+            if (super.processMessage(message)) return true;
+
             maybeLogMessage(this, message.what);
             boolean retValue = true;
             switch (message.what) {
-                case CMD_TETHER_UNREQUESTED:
-                    transitionTo(mInitialState);
-                    if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName);
-                    break;
-                case CMD_INTERFACE_DOWN:
-                    transitionTo(mUnavailableState);
-                    if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName);
+                case CMD_TETHER_REQUESTED:
+                    mLog.e("CMD_TETHER_REQUESTED while already tethering.");
                     break;
                 case CMD_TETHER_CONNECTION_CHANGED:
                     String newUpstreamIfaceName = (String)(message.obj);
@@ -308,7 +410,7 @@
                             mNMService.startInterfaceForwarding(mIfaceName,
                                     newUpstreamIfaceName);
                         } catch (Exception e) {
-                            Log.e(TAG, "Exception enabling Nat: " + e.toString());
+                            mLog.e("Exception enabling NAT: " + e);
                             cleanupUpstreamInterface(newUpstreamIfaceName);
                             mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
                             transitionTo(mInitialState);
@@ -317,18 +419,6 @@
                     }
                     mMyUpstreamIfaceName = newUpstreamIfaceName;
                     break;
-                case CMD_IPV6_TETHER_UPDATE:
-                    mIPv6TetherSvc.updateUpstreamIPv6LinkProperties(
-                            (LinkProperties) message.obj);
-                    break;
-                case CMD_IP_FORWARDING_ENABLE_ERROR:
-                case CMD_IP_FORWARDING_DISABLE_ERROR:
-                case CMD_START_TETHERING_ERROR:
-                case CMD_STOP_TETHERING_ERROR:
-                case CMD_SET_DNS_FORWARDERS_ERROR:
-                    mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
-                    transitionTo(mInitialState);
-                    break;
                 default:
                     retValue = false;
                     break;
@@ -348,9 +438,7 @@
         @Override
         public void enter() {
             mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
-            mTetherController.notifyInterfaceStateChange(
-                    mIfaceName, TetherInterfaceStateMachine.this,
-                    IControlsTethering.STATE_UNAVAILABLE, mLastError);
+            sendInterfaceState(IControlsTethering.STATE_UNAVAILABLE);
         }
     }
 }
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 d38beb3..44c61f0 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.content.Context.TELEPHONY_SERVICE;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
@@ -47,9 +48,9 @@
 public class TetheringConfiguration {
     private static final String TAG = TetheringConfiguration.class.getSimpleName();
 
-    private static final int DUN_NOT_REQUIRED = 0;
-    private static final int DUN_REQUIRED = 1;
-    private static final int DUN_UNSPECIFIED = 2;
+    public static final int DUN_NOT_REQUIRED = 0;
+    public static final int DUN_REQUIRED = 1;
+    public static final int DUN_UNSPECIFIED = 2;
 
     // USB is  192.168.42.1 and 255.255.255.0
     // Wifi is 192.168.43.1 and 255.255.255.0
@@ -81,8 +82,9 @@
         tetherableBluetoothRegexs = ctx.getResources().getStringArray(
                 com.android.internal.R.array.config_tether_bluetooth_regexs);
 
-        isDunRequired = checkDunRequired(ctx);
-        preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, isDunRequired);
+        final int dunCheck = checkDunRequired(ctx);
+        preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, dunCheck);
+        isDunRequired = preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN);
 
         dhcpRanges = getDhcpRanges(ctx);
         defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
@@ -138,14 +140,12 @@
         pw.println();
     }
 
-    private static boolean checkDunRequired(Context ctx) {
-        final TelephonyManager tm = ctx.getSystemService(TelephonyManager.class);
-        final int secureSetting =
-                (tm != null) ? tm.getTetherApnRequired() : DUN_UNSPECIFIED;
-        return (secureSetting == DUN_REQUIRED);
+    private static int checkDunRequired(Context ctx) {
+        final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
+        return (tm != null) ? tm.getTetherApnRequired() : DUN_UNSPECIFIED;
     }
 
-    private static Collection<Integer> getUpstreamIfaceTypes(Context ctx, boolean requiresDun) {
+    private static Collection<Integer> getUpstreamIfaceTypes(Context ctx, int dunCheck) {
         final int ifaceTypes[] = ctx.getResources().getIntArray(
                 com.android.internal.R.array.config_tether_upstream_types);
         final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
@@ -153,10 +153,10 @@
             switch (i) {
                 case TYPE_MOBILE:
                 case TYPE_MOBILE_HIPRI:
-                    if (requiresDun) continue;
+                    if (dunCheck == DUN_REQUIRED) continue;
                     break;
                 case TYPE_MOBILE_DUN:
-                    if (!requiresDun) continue;
+                    if (dunCheck == DUN_NOT_REQUIRED) continue;
                     break;
             }
             upstreamIfaceTypes.add(i);
@@ -166,7 +166,7 @@
         // of the value of |requiresDun|, cell data of one form or another is
         // *always* an upstream, regardless of the upstream interface types
         // specified by configuration resources.
-        if (requiresDun) {
+        if (dunCheck == DUN_REQUIRED) {
             if (!upstreamIfaceTypes.contains(TYPE_MOBILE_DUN)) {
                 upstreamIfaceTypes.add(TYPE_MOBILE_DUN);
             }
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
new file mode 100644
index 0000000..be2cf08
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+
+/**
+ * Capture tethering dependencies, for injection.
+ *
+ * @hide
+ */
+public class TetheringDependencies {
+    public OffloadHardwareInterface getOffloadHardwareInterface() {
+        return new OffloadHardwareInterface();
+    }
+}
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 6209929..be71490 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -29,6 +29,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.NetworkState;
+import android.net.util.SharedLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -73,6 +74,7 @@
     private static final int CALLBACK_MOBILE_REQUEST = 3;
 
     private final Context mContext;
+    private final SharedLog mLog;
     private final StateMachine mTarget;
     private final Handler mHandler;
     private final int mWhat;
@@ -84,16 +86,18 @@
     private boolean mDunRequired;
     private Network mCurrentDefault;
 
-    public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
+    public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what, SharedLog log) {
         mContext = ctx;
         mTarget = tgt;
         mHandler = mTarget.getHandler();
         mWhat = what;
+        mLog = log.forSubComponent(TAG);
     }
 
     @VisibleForTesting
-    public UpstreamNetworkMonitor(StateMachine tgt, int what, ConnectivityManager cm) {
-        this(null, tgt, what);
+    public UpstreamNetworkMonitor(
+            StateMachine tgt, int what, ConnectivityManager cm, SharedLog log) {
+        this(null, tgt, what, log);
         mCM = cm;
     }
 
@@ -136,7 +140,7 @@
 
     public void registerMobileNetworkRequest() {
         if (mMobileNetworkCallback != null) {
-            Log.e(TAG, "registerMobileNetworkRequest() already registered");
+            mLog.e("registerMobileNetworkRequest() already registered");
             return;
         }
 
@@ -156,7 +160,7 @@
         // TODO: Change the timeout from 0 (no onUnavailable callback) to some
         // moderate callback timeout. This might be useful for updating some UI.
         // Additionally, we log a message to aid in any subsequent debugging.
-        Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
+        mLog.i("requesting mobile upstream network: " + mobileUpstreamRequest);
 
         cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback, 0, legacyType, mHandler);
     }
@@ -308,7 +312,8 @@
     // Fetch (and cache) a ConnectivityManager only if and when we need one.
     private ConnectivityManager cm() {
         if (mCM == null) {
-            mCM = mContext.getSystemService(ConnectivityManager.class);
+            // MUST call the String variant to be able to write unittests.
+            mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         }
         return mCM;
     }
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index eead114..6d64b12 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -129,10 +129,11 @@
                 brightnessMode = mLastBrightnessMode;
             }
 
-            if ((color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS ||
-                    mBrightnessMode != brightnessMode)) {
+            if (!mInitialized || color != mColor || mode != mMode || onMS != mOnMS ||
+                    offMS != mOffMS || mBrightnessMode != brightnessMode) {
                 if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#"
                         + Integer.toHexString(color) + ": brightnessMode=" + brightnessMode);
+                mInitialized = true;
                 mLastColor = mColor;
                 mColor = color;
                 mMode = mode;
@@ -164,6 +165,7 @@
         private int mLastColor;
         private boolean mVrModeEnabled;
         private boolean mUseLowPersistenceForVR;
+        private boolean mInitialized;
     }
 
     public LightsService(Context context) {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 4c58ffd..f97a672 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -36,6 +36,7 @@
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
 import android.media.session.IActiveSessionsListener;
+import android.media.session.ICallback;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.media.session.ISessionManager;
@@ -101,6 +102,7 @@
     private AudioManagerInternal mAudioManagerInternal;
     private ContentResolver mContentResolver;
     private SettingsObserver mSettingsObserver;
+    private ICallback mCallback;
 
     // List of user IDs running in the foreground.
     // Multiple users can be in the foreground if the work profile is on.
@@ -485,6 +487,7 @@
             if (size > 0 && records.get(0).isPlaybackActive(false)) {
                 rememberMediaButtonReceiverLocked(records.get(0));
             }
+            pushAddressedPlayerChangedLocked();
             ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
             for (int i = 0; i < size; i++) {
                 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
@@ -516,6 +519,52 @@
         }
     }
 
+    private MediaSessionRecord getMediaButtonSessionLocked() {
+        // If we don't have a media button receiver to fall back on
+        // include non-playing sessions for dispatching.
+        boolean useNotPlayingSessions = true;
+        for (int userId : mCurrentUserIdList) {
+            UserRecord ur = mUserRecords.get(userId);
+            if (ur.mLastMediaButtonReceiver != null
+                    || ur.mRestoredMediaButtonReceiver != null) {
+                useNotPlayingSessions = false;
+                break;
+            }
+        }
+        return mPriorityStack.getDefaultMediaButtonSession(
+                mCurrentUserIdList, useNotPlayingSessions);
+    }
+
+    private void pushAddressedPlayerChangedLocked() {
+        if (mCallback == null) {
+            return;
+        }
+        try {
+            MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
+            if (mediaButtonSession != null) {
+                mCallback.onAddressedPlayerChangedToMediaSession(
+                        new MediaSession.Token(mediaButtonSession.getControllerBinder()));
+            } else {
+                for (int userId : mCurrentUserIdList) {
+                    UserRecord user = mUserRecords.get(userId);
+                    if (user.mLastMediaButtonReceiver == null
+                            && user.mRestoredMediaButtonReceiver == null) {
+                        continue;
+                    }
+                    ComponentName componentName = user.mLastMediaButtonReceiver != null
+                            ? user.mLastMediaButtonReceiver.getIntent().getComponent()
+                            : user.mRestoredMediaButtonReceiver;
+                    mCallback.onAddressedPlayerChangedToMediaButtonReceiver(componentName);
+                    return;
+                }
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
+        }
+    }
+
+    // Remember media button receiver and keep it in the persistent storage.
+    // This should be called whenever there's no media session to receive media button event.
     private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
         PendingIntent receiver = record.getMediaButtonReceiver();
         UserRecord user = mUserRecords.get(record.getUserId());
@@ -530,6 +579,14 @@
         }
     }
 
+    private String getCallingPackageName(int uid) {
+        String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
+            if (packages != null && packages.length > 0) {
+                return packages[0];
+            }
+        return "";
+    }
+
     /**
      * Information about a particular user. The contents of this object is
      * guarded by mLock.
@@ -792,12 +849,10 @@
                         Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions="
                                 + useNotPlayingSessions);
                     }
-                    MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession(
-                            mCurrentUserIdList, useNotPlayingSessions);
                     if (isVoiceKey(keyEvent.getKeyCode())) {
-                        handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
+                        handleVoiceKeyEventLocked(keyEvent, needWakeLock);
                     } else {
-                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
+                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
                     }
                 }
             } finally {
@@ -806,6 +861,45 @@
         }
 
         @Override
+        public void setCallback(ICallback callback) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!UserHandle.isSameApp(uid, Process.BLUETOOTH_UID)) {
+                    throw new SecurityException("Only Bluetooth service processes can set"
+                            + " Callback");
+                }
+                synchronized (mLock) {
+                    Log.d(TAG, "Callback + " + mCallback
+                            + " is set by " + getCallingPackageName(uid));
+                    mCallback = callback;
+                    if (mCallback == null) {
+                        return;
+                    }
+                    try {
+                        mCallback.asBinder().linkToDeath(
+                                new IBinder.DeathRecipient() {
+                                    @Override
+                                    public void binderDied() {
+                                        synchronized (mLock) {
+                                            mCallback = null;
+                                        }
+                                    }
+                                }, 0);
+                        pushAddressedPlayerChangedLocked();
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to set callback", e);
+                        mCallback = null;
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+
+        @Override
         public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
             final long token = Binder.clearCallingIdentity();
             try {
@@ -932,13 +1026,7 @@
             }
         }
 
-        private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
-                MediaSessionRecord session) {
-            if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
-                // If the phone app has priority just give it the event
-                dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
-                return;
-            }
+        private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
             int action = keyEvent.getAction();
             boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
             if (action == KeyEvent.ACTION_DOWN) {
@@ -955,15 +1043,15 @@
                     if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
                         // Resend the down then send this event through
                         KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
-                        dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
-                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
+                        dispatchMediaKeyEventLocked(downEvent, needWakeLock);
+                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
                     }
                 }
             }
         }
 
-        private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
-                MediaSessionRecord session) {
+        private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
+            MediaSessionRecord session = getMediaButtonSessionLocked();
             if (session != null) {
                 if (DEBUG_MEDIA_KEY_EVENT) {
                     Log.d(TAG, "Sending " + keyEvent + " to " + session);
@@ -977,6 +1065,14 @@
                         needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                         mKeyEventReceiver, Process.SYSTEM_UID,
                         getContext().getPackageName());
+                if (mCallback != null) {
+                    try {
+                        mCallback.onMediaKeyEventDispatchedToMediaSession(keyEvent,
+                                new MediaSession.Token(session.getControllerBinder()));
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to send callback", e);
+                    }
+                }
             } else {
                 // Launch the last PendingIntent we had with priority
                 for (int userId : mCurrentUserIdList) {
@@ -993,26 +1089,41 @@
                     mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
                     try {
                         if (user.mLastMediaButtonReceiver != null) {
+                            PendingIntent receiver = user.mLastMediaButtonReceiver;
                             if (DEBUG_MEDIA_KEY_EVENT) {
                                 Log.d(TAG, "Sending " + keyEvent
-                                        + " to the last known pendingIntent "
-                                        + user.mLastMediaButtonReceiver);
+                                        + " to the last known pendingIntent " + receiver);
                             }
-                            user.mLastMediaButtonReceiver.send(getContext(),
+                            receiver.send(getContext(),
                                     needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                                     mediaButtonIntent, mKeyEventReceiver, mHandler);
+                            if (mCallback != null) {
+                                ComponentName componentName =
+                                        user.mLastMediaButtonReceiver.getIntent().getComponent();
+                                if (componentName != null) {
+                                    mCallback.onMediaKeyEventDispatchedToMediaButtonReceiver(
+                                            keyEvent, componentName);
+                                }
+                            }
                         } else {
+                            ComponentName receiver = user.mRestoredMediaButtonReceiver;
                             if (DEBUG_MEDIA_KEY_EVENT) {
                                 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
-                                        + user.mRestoredMediaButtonReceiver);
+                                        + receiver);
                             }
-                            mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver);
+                            mediaButtonIntent.setComponent(receiver);
                             getContext().sendBroadcastAsUser(mediaButtonIntent,
                                     UserHandle.of(userId));
+                            if (mCallback != null) {
+                                mCallback.onMediaKeyEventDispatchedToMediaButtonReceiver(
+                                        keyEvent, receiver);
+                            }
                         }
                     } catch (CanceledException e) {
                         Log.i(TAG, "Error sending key event to media button receiver "
                                 + user.mLastMediaButtonReceiver, e);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to send callback", e);
                     }
                     return;
                 }
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 4a8539a..b40a7e5 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -139,7 +139,6 @@
                 " " + mAcceptedEgressIface + "->" + egressIface);
 
         if (egressDisconnected || egressChanged) {
-            clearSourceRulesLocked();
             mAcceptedEgressIface = null;
             mVpn.stopLegacyVpnPrivileged();
         }
@@ -191,24 +190,6 @@
             EventLogTags.writeLockdownVpnConnected(egressType);
             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
 
-            try {
-                clearSourceRulesLocked();
-
-                mNetService.setFirewallInterfaceRule(iface, true);
-                for (LinkAddress addr : sourceAddrs) {
-                    setFirewallEgressSourceRule(addr, true);
-                }
-
-                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_ALLOW);
-                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, Os.getuid(), FIREWALL_RULE_ALLOW);
-
-                mErrorCount = 0;
-                mAcceptedIface = iface;
-                mAcceptedSourceAddr = sourceAddrs;
-            } catch (RemoteException e) {
-                throw new RuntimeException("Problem setting firewall rules", e);
-            }
-
             final NetworkInfo clone = new NetworkInfo(egressInfo);
             augmentNetworkInfo(clone);
             mConnService.sendConnectedBroadcast(clone);
@@ -225,19 +206,11 @@
         Slog.d(TAG, "initLocked()");
 
         mVpn.setEnableTeardown(false);
+        mVpn.setLockdown(true);
 
         final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
         mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
 
-        try {
-            // TODO: support non-standard port numbers
-            mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
-            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
-            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Problem setting firewall rules", e);
-        }
-
         handleStateChangedLocked();
     }
 
@@ -254,14 +227,7 @@
         mErrorCount = 0;
 
         mVpn.stopLegacyVpnPrivileged();
-        try {
-            mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
-            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
-            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Problem setting firewall rules", e);
-        }
-        clearSourceRulesLocked();
+        mVpn.setLockdown(false);
         hideNotification();
 
         mContext.unregisterReceiver(mResetReceiver);
@@ -278,35 +244,6 @@
         }
     }
 
-    private void clearSourceRulesLocked() {
-        try {
-            if (mAcceptedIface != null) {
-                mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
-                mAcceptedIface = null;
-            }
-            if (mAcceptedSourceAddr != null) {
-                for (LinkAddress addr : mAcceptedSourceAddr) {
-                    setFirewallEgressSourceRule(addr, false);
-                }
-
-                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_DEFAULT);
-                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE,Os.getuid(), FIREWALL_RULE_DEFAULT);
-
-                mAcceptedSourceAddr = null;
-            }
-        } catch (RemoteException e) {
-            throw new RuntimeException("Problem setting firewall rules", e);
-        }
-    }
-
-    private void setFirewallEgressSourceRule(
-            LinkAddress address, boolean allow) throws RemoteException {
-        // Our source address based firewall rules must only cover our own source address, not the
-        // whole subnet
-        final String addrString = address.getAddress().getHostAddress();
-        mNetService.setFirewallEgressSourceRule(addrString, allow);
-    }
-
     public void onNetworkInfoChanged() {
         synchronized (mStateLock) {
             handleStateChangedLocked();
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 42a0bad..f69fe64 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -42,22 +42,20 @@
      * **************************************************************************/
     /** Application should be visible to everyone */
     public static final int DEXOPT_PUBLIC         = 1 << 1;
-    /** Application wants to run in VM safe mode */
-    public static final int DEXOPT_SAFEMODE       = 1 << 2;
     /** Application wants to allow debugging of its code */
-    public static final int DEXOPT_DEBUGGABLE     = 1 << 3;
+    public static final int DEXOPT_DEBUGGABLE     = 1 << 2;
     /** The system boot has finished */
-    public static final int DEXOPT_BOOTCOMPLETE   = 1 << 4;
+    public static final int DEXOPT_BOOTCOMPLETE   = 1 << 3;
     /** Hint that the dexopt type is profile-guided. */
-    public static final int DEXOPT_PROFILE_GUIDED = 1 << 5;
+    public static final int DEXOPT_PROFILE_GUIDED = 1 << 4;
     /** The compilation is for a secondary dex file. */
-    public static final int DEXOPT_SECONDARY_DEX  = 1 << 6;
+    public static final int DEXOPT_SECONDARY_DEX  = 1 << 5;
     /** Ignore the result of dexoptNeeded and force compilation. */
-    public static final int DEXOPT_FORCE          = 1 << 7;
+    public static final int DEXOPT_FORCE          = 1 << 6;
     /** Indicates that the dex file passed to dexopt in on CE storage. */
-    public static final int DEXOPT_STORAGE_CE     = 1 << 8;
+    public static final int DEXOPT_STORAGE_CE     = 1 << 7;
     /** Indicates that the dex file passed to dexopt in on DE storage. */
-    public static final int DEXOPT_STORAGE_DE     = 1 << 9;
+    public static final int DEXOPT_STORAGE_DE     = 1 << 8;
 
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 1498693..d93d620 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -94,9 +94,6 @@
     public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
         this.mContext = context;
         this.mPackageManagerService = packageManagerService;
-
-        // Now it's time to check whether we need to move any A/B artifacts.
-        moveAbArtifacts(packageManagerService.mInstaller);
     }
 
     public static OtaDexoptService main(Context context,
@@ -104,6 +101,9 @@
         OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
         ServiceManager.addService("otadexopt", ota);
 
+        // Now it's time to check whether we need to move any A/B artifacts.
+        ota.moveAbArtifacts(packageManagerService.mInstaller);
+
         return ota;
     }
 
@@ -139,7 +139,7 @@
             // (by default is speed-profile) they will be interepreted/JITed. This in itself is
             // not a problem as we will end up doing profile guided compilation. However, some
             // core apps may be loaded by system server which doesn't JIT and we need to make
-            // sure we don't interpret-only
+            // sure we are not interpreting all their code in that process.
             int compilationReason = p.coreApp
                     ? PackageManagerService.REASON_CORE_APP
                     : PackageManagerService.REASON_AB_OTA;
@@ -330,8 +330,15 @@
             throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
         }
 
+        if (!mPackageManagerService.isUpgrade()) {
+            Slog.d(TAG, "No upgrade, skipping A/B artifacts check.");
+            return;
+        }
+
         // Look into all packages.
         Collection<PackageParser.Package> pkgs = mPackageManagerService.getPackages();
+        int packagePaths = 0;
+        int pathsSuccessful = 0;
         for (PackageParser.Package pkg : pkgs) {
             if (pkg == null) {
                 continue;
@@ -362,13 +369,16 @@
 
                     // TODO: Check first whether there is an artifact, to save the roundtrip time.
 
+                    packagePaths++;
                     try {
                         installer.moveAb(path, dexCodeInstructionSet, oatDir);
+                        pathsSuccessful++;
                     } catch (InstallerException e) {
                     }
                 }
             }
         }
+        Slog.i(TAG, "Moved " + pathsSuccessful + "/" + packagePaths);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index d9ea728..126ad26 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -44,7 +44,6 @@
 import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
 import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
 import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
-import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
 import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX;
 import static com.android.server.pm.Installer.DEXOPT_FORCE;
 import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
@@ -52,7 +51,8 @@
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getNonProfileGuidedCompilerFilter;
+import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
+import static dalvik.system.DexFile.getSafeModeCompilerFilter;
 import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
 
 /**
@@ -150,10 +150,14 @@
 
         // TODO(calin,jeffhao): shared library paths should be adjusted to include previous code
         // paths (b/34169257).
-        final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
+        String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
+        // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
         final int dexoptFlags = getDexFlags(pkg, compilerFilter);
 
         int result = DEX_OPT_SKIPPED;
+        // TODO: Iterate based on dependency hierarchy (currently alphabetically by name)
+        // (b/37480811).
+        String basePathCheck = null;
         for (String path : paths) {
             for (String dexCodeIsa : dexCodeInstructionSets) {
                 int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated,
@@ -165,6 +169,22 @@
                 if ((result != DEX_OPT_FAILED) && (newResult != DEX_OPT_SKIPPED)) {
                     result = newResult;
                 }
+                // Add the relative path of code we just compiled to the shared libraries.
+                int slashIndex = path.lastIndexOf('/') + 1;
+                String relativePath = path.substring(slashIndex);
+                if (sharedLibrariesPath == null) {
+                    sharedLibrariesPath = relativePath;
+                } else {
+                    sharedLibrariesPath += ":" + relativePath;
+                }
+                // Sanity check that the base paths are all the same.
+                String basePath = path.substring(0, slashIndex);
+                if (basePathCheck == null) {
+                    basePathCheck = basePath;
+                } else if (!basePath.equals(basePathCheck)) {
+                    Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
+                        basePathCheck);
+                }
             }
         }
         return result;
@@ -339,13 +359,7 @@
         int flags = info.flags;
         boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
         if (vmSafeMode) {
-            // For the compilation, it doesn't really matter what we return here because installd
-            // will replace the filter with interpret-only anyway.
-            // However, we return a non profile guided filter so that we simplify the logic of
-            // merging profiles.
-            // TODO(calin): safe mode path could be simplified if we pass interpret-only from
-            //              here rather than letting installd decide on the filter.
-            return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
+            return getSafeModeCompilerFilter(targetCompilerFilter);
         }
 
         if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) {
@@ -366,7 +380,6 @@
 
     private int getDexFlags(ApplicationInfo info, String compilerFilter) {
         int flags = info.flags;
-        boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
         boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
         // Profile guide compiled oat files should not be public.
         boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter);
@@ -374,7 +387,6 @@
         int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
         int dexFlags =
                 (isPublic ? DEXOPT_PUBLIC : 0)
-                | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
                 | (debuggable ? DEXOPT_DEBUGGABLE : 0)
                 | profileFlag
                 | DEXOPT_BOOTCOMPLETE;
@@ -493,9 +505,6 @@
         if ((flags & DEXOPT_PUBLIC) == DEXOPT_PUBLIC) {
             flagsList.add("public");
         }
-        if ((flags & DEXOPT_SAFEMODE) == DEXOPT_SAFEMODE) {
-            flagsList.add("safemode");
-        }
         if ((flags & DEXOPT_SECONDARY_DEX) == DEXOPT_SECONDARY_DEX) {
             flagsList.add("secondary");
         }
@@ -529,9 +538,13 @@
 
         @Override
         protected int adjustDexoptNeeded(int dexoptNeeded) {
-            // Ensure compilation, no matter the current state.
-            // TODO: The return value is wrong when patchoat is needed.
-            return DexFile.DEX2OAT_FROM_SCRATCH;
+            if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) {
+                // Ensure compilation by pretending a compiler filter change on the
+                // apk/odex location (the reason for the '-'. A positive value means
+                // the 'oat' location).
+                return -DexFile.DEX2OAT_FOR_FILTER;
+            }
+            return dexoptNeeded;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b207b81..2391abd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -92,11 +92,12 @@
 import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getFullCompilerFilter;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getNonProfileGuidedCompilerFilter;
 import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_FAILURE;
 import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS;
 import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
 
+import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -7364,20 +7365,7 @@
             // are verify-profile but for preopted apps there's no profile.
             // Do a hacky check to ensure that if we have no profiles (a reasonable indication
             // that before the OTA the app was preopted) the app gets compiled with a non-profile
-            // filter (by default interpret-only).
-            // Note that at this stage unused apps are already filtered.
-            if (isSystemApp(pkg) &&
-                    DexFile.isProfileGuidedCompilerFilter(compilerFilter) &&
-                    !Environment.getReferenceProfile(pkg.packageName).exists()) {
-                compilerFilter = getNonProfileGuidedCompilerFilter(compilerFilter);
-            }
-
-            // If the OTA updates a system app which was previously preopted to a non-preopted state
-            // the app might end up being verified at runtime. That's because by default the apps
-            // are verify-profile but for preopted apps there's no profile.
-            // Do a hacky check to ensure that if we have no profiles (a reasonable indication
-            // that before the OTA the app was preopted) the app gets compiled with a non-profile
-            // filter (by default interpret-only).
+            // filter (by default 'quicken').
             // Note that at this stage unused apps are already filtered.
             if (isSystemApp(pkg) &&
                     DexFile.isProfileGuidedCompilerFilter(compilerFilter) &&
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 0634dac..f56534d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -123,11 +123,4 @@
 
         return value;
     }
-
-    /**
-     * Return the non-profile-guided filter corresponding to the given filter.
-     */
-    public static String getNonProfileGuidedCompilerFilter(String filter) {
-        return DexFile.getNonProfileGuidedCompilerFilter(filter);
-    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1bfc157..2f000c2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1454,10 +1454,10 @@
         pw.println("      -f: force compilation even if not needed");
         pw.println("      -m: select compilation mode");
         pw.println("          MODE is one of the dex2oat compiler filters:");
-        pw.println("            verify-none");
-        pw.println("            verify-at-runtime");
-        pw.println("            verify-profile");
-        pw.println("            interpret-only");
+        pw.println("            assume-verified");
+        pw.println("            extract");
+        pw.println("            verify");
+        pw.println("            quicken");
         pw.println("            space-profile");
         pw.println("            space");
         pw.println("            speed-profile");
diff --git a/services/core/java/com/android/server/timezone/CheckToken.java b/services/core/java/com/android/server/timezone/CheckToken.java
new file mode 100644
index 0000000..5128360
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/CheckToken.java
@@ -0,0 +1,98 @@
+/*
+ * 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 java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * A deserialized version of the byte[] sent to the time zone update application to identify a
+ * triggered time zone update check. It encodes the optimistic lock ID used to detect
+ * concurrent checks and the minimal package versions that will have been checked.
+ */
+final class CheckToken {
+
+    final int mOptimisticLockId;
+    final PackageVersions mPackageVersions;
+
+    CheckToken(int optimisticLockId, PackageVersions packageVersions) {
+        this.mOptimisticLockId = optimisticLockId;
+
+        if (packageVersions == null) {
+            throw new NullPointerException("packageVersions == null");
+        }
+        this.mPackageVersions = packageVersions;
+    }
+
+    byte[] toByteArray() {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(12 /* (3 * sizeof(int)) */);
+        try (DataOutputStream dos = new DataOutputStream(baos)) {
+            dos.writeInt(mOptimisticLockId);
+            dos.writeInt(mPackageVersions.mUpdateAppVersion);
+            dos.writeInt(mPackageVersions.mDataAppVersion);
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to write into a ByteArrayOutputStream", e);
+        }
+        return baos.toByteArray();
+    }
+
+    static CheckToken fromByteArray(byte[] tokenBytes) throws IOException {
+        ByteArrayInputStream bais = new ByteArrayInputStream(tokenBytes);
+        try (DataInputStream dis = new DataInputStream(bais)) {
+            int versionId = dis.readInt();
+            int updateAppVersion = dis.readInt();
+            int dataAppVersion = dis.readInt();
+            return new CheckToken(versionId, new PackageVersions(updateAppVersion, dataAppVersion));
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        CheckToken checkToken = (CheckToken) o;
+
+        if (mOptimisticLockId != checkToken.mOptimisticLockId) {
+            return false;
+        }
+        return mPackageVersions.equals(checkToken.mPackageVersions);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mOptimisticLockId;
+        result = 31 * result + mPackageVersions.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Token{" +
+                "mOptimisticLockId=" + mOptimisticLockId +
+                ", mPackageVersions=" + mPackageVersions +
+                '}';
+    }
+}
diff --git a/services/core/java/com/android/server/timezone/ClockHelper.java b/services/core/java/com/android/server/timezone/ClockHelper.java
new file mode 100644
index 0000000..353728a
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/ClockHelper.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/**
+ * An easy-to-mock interface for obtaining a monotonically increasing time value in milliseconds.
+ */
+interface ClockHelper {
+
+    long currentTimestamp();
+}
diff --git a/services/core/java/com/android/server/timezone/ConfigHelper.java b/services/core/java/com/android/server/timezone/ConfigHelper.java
new file mode 100644
index 0000000..f9984fa
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/ConfigHelper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface around device config for use by {@link PackageTracker}; it is not
+ * possible to test various states with the real one because config is fixed in the system image.
+ */
+interface ConfigHelper {
+
+    boolean isTrackingEnabled();
+
+    String getUpdateAppPackageName();
+
+    String getDataAppPackageName();
+
+    int getCheckTimeAllowedMillis();
+
+    int getFailedCheckRetryCount();
+}
diff --git a/services/core/java/com/android/server/timezone/FileDescriptorHelper.java b/services/core/java/com/android/server/timezone/FileDescriptorHelper.java
new file mode 100644
index 0000000..c3b1101
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/FileDescriptorHelper.java
@@ -0,0 +1,30 @@
+/*
+ * 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/IntentHelper.java b/services/core/java/com/android/server/timezone/IntentHelper.java
new file mode 100644
index 0000000..0cb9065
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/IntentHelper.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * An easy-to-mock interface around intent sending / receiving for use by {@link PackageTracker};
+ * it is not possible to test various cases with the real one because of the need to simulate
+ * receiving and broadcasting intents.
+ */
+interface IntentHelper {
+
+    void initialize(String updateAppPackageName, String dataAppPackageName, Listener listener);
+
+    void sendTriggerUpdateCheck(CheckToken checkToken);
+
+    void enableReliabilityTriggering();
+
+    void disableReliabilityTriggering();
+
+    interface Listener {
+        void triggerUpdateIfNeeded(boolean packageUpdated);
+    }
+}
diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
new file mode 100644
index 0000000..3ffbb2d
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
@@ -0,0 +1,116 @@
+/*
+ * 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.app.timezone.RulesUpdaterContract;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PatternMatcher;
+import android.util.Slog;
+
+import java.util.regex.Pattern;
+
+/**
+ * The bona fide implementation of {@link IntentHelper}.
+ */
+final class IntentHelperImpl implements IntentHelper {
+
+    private final static String TAG = "timezone.IntentHelperImpl";
+
+    private final Context mContext;
+    private String mUpdaterAppPackageName;
+
+    private boolean mReliabilityReceiverEnabled;
+    private Receiver mReliabilityReceiver;
+
+    IntentHelperImpl(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void initialize(
+            String updaterAppPackageName, String dataAppPackageName, Listener listener) {
+        mUpdaterAppPackageName = updaterAppPackageName;
+
+        // Register for events of interest.
+
+        // The intent filter that triggers when package update events happen that indicate there may
+        // be work to do.
+        IntentFilter packageIntentFilter = new IntentFilter();
+        // Either of these mean a downgrade?
+        packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageIntentFilter.addDataScheme("package");
+        packageIntentFilter.addDataSchemeSpecificPart(
+                updaterAppPackageName, PatternMatcher.PATTERN_LITERAL);
+        packageIntentFilter.addDataSchemeSpecificPart(
+                dataAppPackageName, PatternMatcher.PATTERN_LITERAL);
+        Receiver packageUpdateReceiver = new Receiver(listener, true /* packageUpdated */);
+        mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
+
+        // TODO(nfuller): Add more exotic intents as needed. e.g.
+        // packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        // Also, disabled...?
+        mReliabilityReceiver = new Receiver(listener, false /* packageUpdated */);
+    }
+
+    /** Sends an intent to trigger an update check. */
+    @Override
+    public void sendTriggerUpdateCheck(CheckToken checkToken) {
+        RulesUpdaterContract.sendBroadcast(
+                mContext, mUpdaterAppPackageName, checkToken.toByteArray());
+    }
+
+    @Override
+    public synchronized void enableReliabilityTriggering() {
+        if (!mReliabilityReceiverEnabled) {
+            // The intent filter that exists to make updates reliable in the event of failures /
+            // reboots.
+            IntentFilter reliabilityIntentFilter = new IntentFilter();
+            reliabilityIntentFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
+            mContext.registerReceiver(mReliabilityReceiver, reliabilityIntentFilter);
+            mReliabilityReceiverEnabled = true;
+        }
+    }
+
+    @Override
+    public synchronized void disableReliabilityTriggering() {
+        if (mReliabilityReceiverEnabled) {
+            mContext.unregisterReceiver(mReliabilityReceiver);
+            mReliabilityReceiverEnabled = false;
+        }
+    }
+
+    private static class Receiver extends BroadcastReceiver {
+        private final Listener mListener;
+        private final boolean mPackageUpdated;
+
+        private Receiver(Listener listener, boolean packageUpdated) {
+            mListener = listener;
+            mPackageUpdated = packageUpdated;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Slog.d(TAG, "Received intent: " + intent.toString());
+            mListener.triggerUpdateIfNeeded(mPackageUpdated);
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/timezone/PackageManagerHelper.java b/services/core/java/com/android/server/timezone/PackageManagerHelper.java
new file mode 100644
index 0000000..804941a
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageManagerHelper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.content.Intent;
+import android.content.pm.PackageManager;
+
+/**
+ * An easy-to-mock facade around PackageManager for use by {@link PackageTracker}; it is not
+ * possible to test various cases with the real one because of the need to simulate package versions
+ * and manifest configurations.
+ */
+interface PackageManagerHelper {
+
+    int getInstalledPackageVersion(String packageName)
+            throws PackageManager.NameNotFoundException;
+
+    boolean isPrivilegedApp(String packageName) throws PackageManager.NameNotFoundException;
+
+    boolean usesPermission(String packageName, String requiredPermissionName)
+                    throws PackageManager.NameNotFoundException;
+
+    boolean contentProviderRegistered(String authority, String requiredPackageName);
+
+    boolean receiverRegistered(Intent intent, String requiredPermissionName)
+                            throws PackageManager.NameNotFoundException;
+}
diff --git a/services/core/java/com/android/server/timezone/PackageStatus.java b/services/core/java/com/android/server/timezone/PackageStatus.java
new file mode 100644
index 0000000..6379096
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageStatus.java
@@ -0,0 +1,89 @@
+/*
+ * 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.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Information about the status of the time zone update / data packages that are persisted by the
+ * Android system.
+ */
+final class PackageStatus {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ CHECK_STARTED, CHECK_COMPLETED_SUCCESS, CHECK_COMPLETED_FAILURE })
+    @interface CheckStatus {}
+
+    /** A time zone update check has been started but not yet completed. */
+    static final int CHECK_STARTED = 1;
+    /** A time zone update check has been completed and succeeded. */
+    static final int CHECK_COMPLETED_SUCCESS = 2;
+    /** A time zone update check has been completed and failed. */
+    static final int CHECK_COMPLETED_FAILURE = 3;
+
+    @CheckStatus
+    final int mCheckStatus;
+
+    // Non-null
+    final PackageVersions mVersions;
+
+    PackageStatus(@CheckStatus int checkStatus, PackageVersions versions) {
+        this.mCheckStatus = checkStatus;
+        if (checkStatus < 1 || checkStatus > 3) {
+            throw new IllegalArgumentException("Unknown checkStatus " + checkStatus);
+        }
+        if (versions == null) {
+            throw new NullPointerException("versions == null");
+        }
+        this.mVersions = versions;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        PackageStatus that = (PackageStatus) o;
+
+        if (mCheckStatus != that.mCheckStatus) {
+            return false;
+        }
+        return mVersions.equals(that.mVersions);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mCheckStatus;
+        result = 31 * result + mVersions.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "PackageStatus{" +
+                "mCheckStatus=" + mCheckStatus +
+                ", mVersions=" + mVersions +
+                '}';
+    }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageStatusStorage.java b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
new file mode 100644
index 0000000..31f0e31
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
@@ -0,0 +1,336 @@
+/*
+ * 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.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Slog;
+
+import java.io.File;
+
+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;
+
+/**
+ * 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.
+ */
+final class PackageStatusStorage {
+
+    private static final String 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";
+
+    /**
+     * Column 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";
+
+    /**
+     * Column that stores the current "check status" of the time zone update application packages.
+     */
+    private static final String COLUMN_CHECK_STATUS = "check_status";
+
+    /**
+     * Column 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";
+
+    /**
+     * Column 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 int UNKNOWN_PACKAGE_VERSION = -1;
+
+    private final DatabaseHelper mDatabaseHelper;
+
+    PackageStatusStorage(Context context) {
+        mDatabaseHelper = new DatabaseHelper(context);
+    }
+
+    void deleteDatabaseForTests() {
+        SQLiteDatabase.deleteDatabase(mDatabaseHelper.getDatabaseFile());
+    }
+
+    /**
+     * Obtain the current check status of the application packages. Returns {@code null} the first
+     * time it is called, or after {@link #resetCheckState()}.
+     */
+    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);
+
+                // Reset the storage so it is in a good state again.
+                mDatabaseHelper.recoverFromBadData();
+                return getPackageStatusInternal();
+            }
+        }
+    }
+
+    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;
+        }
+        cursor.moveToFirst();
+
+        // Determine check status.
+        if (cursor.isNull(0)) {
+            // This is normal the first time getPackageStatus() is called, or after
+            // resetCheckState().
+            return null;
+        }
+        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));
+
+        return new PackageStatus(checkStatus, packageVersions);
+    }
+
+    /**
+     * Generate a new {@link CheckToken} that can be passed to the time zone rules update
+     * application.
+     */
+    CheckToken generateCheckToken(PackageVersions currentInstalledVersions) {
+        if (currentInstalledVersions == null) {
+            throw new NullPointerException("currentInstalledVersions == null");
+        }
+
+        synchronized (this) {
+            Integer optimisticLockId = getCurrentOptimisticLockId();
+            if (optimisticLockId == null) {
+                Slog.w(TAG, "Unable to find optimistic lock ID from package status row");
+
+                // Recover.
+                optimisticLockId = mDatabaseHelper.recoverFromBadData();
+            }
+
+            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;
+            }
+            return new CheckToken(newOptimisticLockId, currentInstalledVersions);
+        }
+    }
+
+    /**
+     * Reset the current device state to "unknown".
+     */
+    void resetCheckState() {
+        synchronized(this) {
+            Integer optimisticLockId = getCurrentOptimisticLockId();
+            if (optimisticLockId == null) {
+                Slog.w(TAG, "resetCheckState: Unable to find optimistic lock ID from package"
+                        + " status row");
+                // Attempt to recover the storage state.
+                optimisticLockId = mDatabaseHelper.recoverFromBadData();
+            }
+
+            int newOptimisticLockId = optimisticLockId + 1;
+            if (!writeStatusRow(optimisticLockId, newOptimisticLockId,
+                    null /* status */, null /* packageVersions */)) {
+                Slog.e(TAG, "resetCheckState: Unable to reset package status row,"
+                        + " newOptimisticLockId=" + newOptimisticLockId);
+            }
+        }
+    }
+
+    /**
+     * Update the current device state if possible. Returns true if the update was successful.
+     * {@code false} indicates the storage has been changed since the {@link CheckToken} was
+     * generated and the update was discarded.
+     */
+    boolean markChecked(CheckToken checkToken, boolean succeeded) {
+        synchronized (this) {
+            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;
+            }
+            cursor.moveToFirst();
+            return cursor.getInt(0);
+        }
+    }
+
+    // Caller should be synchronized(this)
+    private boolean writeStatusRow(int optimisticLockId, int newOptimisticLockId, Integer status,
+            PackageVersions packageVersions) {
+        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);
+        }
+
+        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);
+    }
+
+    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;
+            }
+            return initialOptimisticLockId;
+        }
+
+        File getDatabaseFile() {
+            return mContext.getDatabasePath(DATABASE_NAME);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageTracker.java b/services/core/java/com/android/server/timezone/PackageTracker.java
new file mode 100644
index 0000000..8abf7df
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageTracker.java
@@ -0,0 +1,504 @@
+/*
+ * 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 com.android.internal.annotations.VisibleForTesting;
+
+import android.app.timezone.RulesUpdaterContract;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.TimeZoneRulesDataContract;
+import android.util.Slog;
+
+/**
+ * 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
+ * broadcast intent is used to trigger the time zone updater app.
+ *
+ * <p>The "update triggering" behavior of this component can be disabled via device configuration.
+ *
+ * <p>The package tracker listens for package updates of the time zone "updater app" and "data app".
+ * It also listens for "reliability" triggers. Reliability triggers are there to ensure that the
+ * package tracker handles failures reliably and are "idle maintenance" events or something similar.
+ * Reliability triggers can cause a time zone update check to take place if the current state is
+ * unclear. For example, it can be unclear after boot or after a failure. If there are repeated
+ * failures reliability updates are halted until the next boot.
+ *
+ * <p>This component keeps persistent track of the most recent app packages checked to avoid
+ * unnecessary expense from broadcasting intents (which will cause other app processes to spawn).
+ * The current status is also stored to detect whether the most recently-generated check is
+ * complete successfully. For example, if the device was interrupted while doing a check and never
+ * acknowledged a check then a check will be retried the next time a "reliability trigger" event
+ * happens.
+ */
+// Also made non-final so it can be mocked.
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class PackageTracker implements IntentHelper.Listener {
+    private static final String TAG = "timezone.PackageTracker";
+
+    private final PackageManagerHelper mPackageManagerHelper;
+    private final IntentHelper mIntentHelper;
+    private final ConfigHelper mConfigHelper;
+    private final PackageStatusStorage mPackageStatusStorage;
+    private final ClockHelper mClockHelper;
+
+    // False if tracking is disabled.
+    private boolean mTrackingEnabled;
+
+    // These fields may be null if package tracking is disabled.
+    private String mUpdateAppPackageName;
+    private String mDataAppPackageName;
+
+    // The time a triggered check is allowed to take before it is considered overdue.
+    private int mCheckTimeAllowedMillis;
+    // The number of failed checks in a row before reliability checks should stop happening.
+    private long mFailedCheckRetryCount;
+
+    // Reliability check state: If a check was triggered but not acknowledged within
+    // mCheckTimeAllowedMillis then another one can be triggered.
+    private Long mLastTriggerTimestamp = null;
+
+    // Reliability check state: Whether any checks have been triggered at all.
+    private boolean mCheckTriggered;
+
+    // Reliability check state: A count of how many failures have occurred consecutively.
+    private int mCheckFailureCount;
+
+    /** Creates the {@link PackageTracker} for normal use. */
+    static PackageTracker create(Context context) {
+        PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context);
+        return new PackageTracker(
+                helperImpl /* clock */,
+                helperImpl /* configHelper */,
+                helperImpl /* packageManagerHelper */,
+                new PackageStatusStorage(context),
+                new IntentHelperImpl(context));
+    }
+
+    // A constructor that can be used by tests to supply mocked / faked dependencies.
+    PackageTracker(ClockHelper clockHelper, ConfigHelper configHelper,
+            PackageManagerHelper packageManagerHelper, PackageStatusStorage packageStatusStorage,
+            IntentHelper intentHelper) {
+        mClockHelper = clockHelper;
+        mConfigHelper = configHelper;
+        mPackageManagerHelper = packageManagerHelper;
+        mPackageStatusStorage = packageStatusStorage;
+        mIntentHelper = intentHelper;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected synchronized void start() {
+        mTrackingEnabled = mConfigHelper.isTrackingEnabled();
+        if (!mTrackingEnabled) {
+            Slog.i(TAG, "Time zone updater / data package tracking explicitly disabled.");
+            return;
+        }
+
+        mUpdateAppPackageName = mConfigHelper.getUpdateAppPackageName();
+        mDataAppPackageName = mConfigHelper.getDataAppPackageName();
+        mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis();
+        mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount();
+
+        // Validate the device configuration including the application packages.
+        // The manifest entries in the apps themselves are not validated until use as they can
+        // change and we don't want to prevent the system server starting due to a bad application.
+        throwIfDeviceSettingsOrAppsAreBad();
+
+        // Explicitly start in a reliability state where reliability triggering will do something.
+        mCheckTriggered = false;
+        mCheckFailureCount = 0;
+
+        // Initialize the intent helper.
+        mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this);
+
+        // Enable the reliability triggering so we will have at least one reliability trigger if
+        // a package isn't updated.
+        mIntentHelper.enableReliabilityTriggering();
+
+        Slog.i(TAG, "Time zone updater / data package tracking enabled");
+    }
+
+    /**
+     * Performs checks that confirm the system image has correctly configured package
+     * tracking configuration. Only called if package tracking is enabled. Throws an exception if
+     * the device is configured badly which will prevent the device booting.
+     */
+    private void throwIfDeviceSettingsOrAppsAreBad() {
+        // None of the checks below can be based on application manifest settings, otherwise a bad
+        // update could leave the device in an unbootable state. See validateDataAppManifest() and
+        // validateUpdaterAppManifest() for softer errors.
+
+        throwRuntimeExceptionIfNullOrEmpty(
+                mUpdateAppPackageName, "Update app package name missing.");
+        throwRuntimeExceptionIfNullOrEmpty(mDataAppPackageName, "Data app package name missing.");
+        if (mFailedCheckRetryCount < 1) {
+            throw logAndThrowRuntimeException("mFailedRetryCount=" + mFailedCheckRetryCount, null);
+        }
+        if (mCheckTimeAllowedMillis < 1000) {
+            throw logAndThrowRuntimeException(
+                    "mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis, null);
+        }
+
+        // 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.
+        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.
+        Slog.d(TAG, "Data app " + mDataAppPackageName + " is valid.");
+    }
+
+    /**
+     * Inspects the current in-memory state, installed packages and storage state to determine if an
+     * update check is needed and then trigger if it is.
+     *
+     * @param packageChanged true if this method was called because a known packaged definitely
+     *     changed, false if the cause is a reliability trigger
+     */
+    @Override
+    public synchronized void triggerUpdateIfNeeded(boolean packageChanged) {
+        if (!mTrackingEnabled) {
+            throw new IllegalStateException("Unexpected call. Tracking is disabled.");
+        }
+
+        // Validate the applications' current manifest entries: make sure they are configured as
+        // they should be. These are not fatal and just means that no update is triggered: we don't
+        // want to take down the system server if an OEM or Google have pushed a bad update to
+        // an application.
+        boolean updaterAppManifestValid = validateUpdaterAppManifest();
+        boolean dataAppManifestValid = validateDataAppManifest();
+        if (!updaterAppManifestValid || !dataAppManifestValid) {
+            Slog.e(TAG, "No update triggered due to invalid application manifest entries."
+                    + " updaterApp=" + updaterAppManifestValid
+                    + ", dataApp=" + dataAppManifestValid);
+
+            // There's no point in doing reliability checks if the current packages are bad.
+            mIntentHelper.disableReliabilityTriggering();
+            return;
+        }
+
+        if (!packageChanged) {
+            // This call was made because the device is doing a "reliability" check.
+            // 4 possible cases:
+            // 1) No check has previously triggered since restart. We want to trigger in this case.
+            // 2) A check has previously triggered and it is in progress. We want to trigger if
+            //    the response is overdue.
+            // 3) A check has previously triggered and it failed. We want to trigger, but only if
+            //    we're not in a persistent failure state.
+            // 4) A check has previously triggered and it succeeded.
+            //    We don't want to trigger, and want to stop future triggers.
+
+            if (!mCheckTriggered) {
+                // Case 1.
+                Slog.d(TAG, "triggerUpdateIfNeeded: First reliability trigger.");
+            } else if (isCheckInProgress()) {
+                // Case 2.
+                if (!isCheckResponseOverdue()) {
+                    // A check is in progress but hasn't been given time to succeed.
+                    Slog.d(TAG,
+                            "triggerUpdateIfNeeded: checkComplete call is not yet overdue."
+                                    + " Not triggering.");
+                    // Not doing any work, but also not disabling future reliability triggers.
+                    return;
+                }
+            } else if (mCheckFailureCount > mFailedCheckRetryCount) {
+                // Case 3. If the system is in some kind of persistent failure state we don't want
+                // to keep checking, so just stop.
+                Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures"
+                        + " exceeded. Stopping reliability triggers until next reboot or package"
+                        + " update.");
+                mIntentHelper.disableReliabilityTriggering();
+                return;
+            } else if (mCheckFailureCount == 0) {
+                // Case 4.
+                Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was"
+                        + " successful.");
+                mIntentHelper.disableReliabilityTriggering();
+                return;
+            }
+        }
+
+        // Read the currently installed data / updater package versions.
+        PackageVersions currentInstalledVersions = lookupInstalledPackageVersions();
+        if (currentInstalledVersions == null) {
+            // This should not happen if the device is configured in a valid way.
+            Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null");
+            mIntentHelper.disableReliabilityTriggering();
+            return;
+        }
+
+        // Establish the current state using package manager and stored state. Determine if we have
+        // already successfully checked the installed versions.
+        PackageStatus packageStatus = mPackageStatusStorage.getPackageStatus();
+        if (packageStatus == null) {
+            // This can imply corrupt, uninitialized storage state (e.g. first check ever on a
+            // device) or after some kind of reset.
+            Slog.i(TAG, "triggerUpdateIfNeeded: No package status data found. Data check needed.");
+        } else if (!packageStatus.mVersions.equals(currentInstalledVersions)) {
+            // The stored package version information differs from the installed version.
+            // Trigger the check in all cases.
+            Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions="
+                    + packageStatus.mVersions + ", do not match current package versions="
+                    + currentInstalledVersions + ". Triggering check.");
+        } else {
+            Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions match currently"
+                    + " installed versions, currentInstalledVersions=" + currentInstalledVersions
+                    + ", packageStatus.mCheckStatus=" + packageStatus.mCheckStatus);
+            if (packageStatus.mCheckStatus == PackageStatus.CHECK_COMPLETED_SUCCESS) {
+                // The last check succeeded and nothing has changed. Do nothing and disable
+                // reliability checks.
+                Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger.");
+                mIntentHelper.disableReliabilityTriggering();
+                return;
+            }
+        }
+
+        // Generate a token to send to the updater app.
+        CheckToken checkToken =
+                mPackageStatusStorage.generateCheckToken(currentInstalledVersions);
+        if (checkToken == null) {
+            Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token."
+                    + " Not sending check request.");
+            return;
+        }
+
+        // Trigger the update check.
+        mIntentHelper.sendTriggerUpdateCheck(checkToken);
+        mCheckTriggered = true;
+
+        // Update the reliability check state in case the update fails.
+        setCheckInProgress();
+
+        // Enable reliability triggering in case the check doesn't succeed and there is no
+        // response at all. Enabling reliability triggering is idempotent.
+        mIntentHelper.enableReliabilityTriggering();
+    }
+
+    /**
+     * Used to record the result of a check. Can be called even if active package tracking is
+     * disabled.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected synchronized void recordCheckResult(CheckToken checkToken, boolean success) {
+        Slog.i(TAG, "recordOperationResult: checkToken=" + checkToken + " success=" + success);
+
+        // If package tracking is disabled it means no record-keeping is required. However, we do
+        // want to clear out any stored state to make it clear that the current state is unknown and
+        // should tracking become enabled again (perhaps through an OTA) we'd need to perform an
+        // update check.
+        if (!mTrackingEnabled) {
+            // This means an updater has spontaneously modified time zone data without having been
+            // triggered. This can happen if the OEM is handling their own updates, but we don't
+            // need to do any tracking in this case.
+
+            if (checkToken == null) {
+                // This is the expected case if tracking is disabled but an OEM is handling time
+                // zone installs using their own mechanism.
+                Slog.d(TAG, "recordCheckResult: Tracking is disabled and no token has been"
+                        + " provided. Resetting tracking state.");
+            } else {
+                // This is unexpected. If tracking is disabled then no check token should have been
+                // generated by the package tracker. An updater should never create its own token.
+                // This could be a bug in the updater.
+                Slog.w(TAG, "recordCheckResult: Tracking is disabled and a token " + checkToken
+                        + " has been unexpectedly provided. Resetting tracking state.");
+            }
+            mPackageStatusStorage.resetCheckState();
+            return;
+        }
+
+        if (checkToken == null) {
+            /*
+             * If the checkToken is null it suggests an install / uninstall / acknowledgement has
+             * occurred without a prior trigger (or the client didn't return the token it was given
+             * for some reason, perhaps a bug).
+             *
+             * This shouldn't happen under normal circumstances:
+             *
+             * If package tracking is enabled, we assume it is the package tracker responsible for
+             * triggering updates and a token should have been produced and returned.
+             *
+             * If the OEM is handling time zone updates case package tracking should be disabled.
+             *
+             * This could happen in tests. The device should recover back to a known state by
+             * itself rather than be left in an invalid state.
+             *
+             * We treat this as putting the device into an unknown state and make sure that
+             * reliability triggering is enabled so we should recover.
+             */
+            Slog.i(TAG, "recordCheckResult: Unexpectedly missing checkToken, resetting"
+                    + " storage state.");
+            mPackageStatusStorage.resetCheckState();
+
+            // Enable reliability triggering and reset the failure count so we know that the
+            // next reliability trigger will do something.
+            mIntentHelper.enableReliabilityTriggering();
+            mCheckFailureCount = 0;
+        } else {
+            // This is the expected case when tracking is enabled: a check was triggered and it has
+            // completed.
+            boolean recordedCheckCompleteSuccessfully =
+                    mPackageStatusStorage.markChecked(checkToken, success);
+            if (recordedCheckCompleteSuccessfully) {
+                // If we have recorded the result (whatever it was) we know there is no check in
+                // progress.
+                setCheckComplete();
+
+                if (success) {
+                    // Since the check was successful, no more reliability checks are required until
+                    // there is a package change.
+                    mIntentHelper.disableReliabilityTriggering();
+                    mCheckFailureCount = 0;
+                } else {
+                    // Enable reliability triggering to potentially check again in future.
+                    mIntentHelper.enableReliabilityTriggering();
+                    mCheckFailureCount++;
+                }
+            } else {
+                // The failure to record the check means an optimistic lock failure and suggests
+                // that another check was triggered after the token was generated.
+                Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken
+                        + " with success=" + success + ". Optimistic lock failure");
+
+                // Enable reliability triggering to potentially try again in future.
+                mIntentHelper.enableReliabilityTriggering();
+                mCheckFailureCount++;
+            }
+        }
+    }
+
+    /** Access to consecutive failure counts for use in tests. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected int getCheckFailureCountForTests() {
+        return mCheckFailureCount;
+    }
+
+    private void setCheckInProgress() {
+        mLastTriggerTimestamp = mClockHelper.currentTimestamp();
+    }
+
+    private void setCheckComplete() {
+        mLastTriggerTimestamp = null;
+    }
+
+    private boolean isCheckInProgress() {
+        return mLastTriggerTimestamp != null;
+    }
+
+    private boolean isCheckResponseOverdue() {
+        if (mLastTriggerTimestamp == null) {
+            return false;
+        }
+        // Risk of overflow, but highly unlikely given the implementation and not problematic.
+        return mClockHelper.currentTimestamp() > mLastTriggerTimestamp + mCheckTimeAllowedMillis;
+    }
+
+    private PackageVersions lookupInstalledPackageVersions() {
+        int updatePackageVersion;
+        int dataPackageVersion;
+        try {
+            updatePackageVersion =
+                    mPackageManagerHelper.getInstalledPackageVersion(mUpdateAppPackageName);
+            dataPackageVersion =
+                    mPackageManagerHelper.getInstalledPackageVersion(mDataAppPackageName);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "lookupInstalledPackageVersions: Unable to resolve installed package"
+                    + " versions", e);
+            return null;
+        }
+        return new PackageVersions(updatePackageVersion, dataPackageVersion);
+    }
+
+    private boolean validateDataAppManifest() {
+        // We only want to talk to a provider that exposed by the known data app package
+        // so we look up the providers exposed by that app and check the well-known authority is
+        // there. This prevents the case where *even if* the data app doesn't expose the provider
+        // required, another app cannot expose one to replace it.
+        if (!mPackageManagerHelper.contentProviderRegistered(
+                TimeZoneRulesDataContract.AUTHORITY, mDataAppPackageName)) {
+            // Error! Found the package but it didn't expose the correct provider.
+            Slog.w(TAG, "validateDataAppManifest: Data app " + mDataAppPackageName
+                    + " does not expose the required provider with authority="
+                    + TimeZoneRulesDataContract.AUTHORITY);
+            return false;
+        }
+        // TODO(nfuller) Add any permissions checks needed.
+        return true;
+    }
+
+    private boolean validateUpdaterAppManifest() {
+        try {
+            // The updater app is expected to have the UPDATE_TIME_ZONE_RULES permission.
+            // The updater app is expected to have a receiver for the intent we are going to trigger
+            // and require the TRIGGER_TIME_ZONE_RULES_CHECK.
+            if (!mPackageManagerHelper.usesPermission(
+                    mUpdateAppPackageName,
+                    RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION)) {
+                Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName
+                        + " does not use permission="
+                        + RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
+                return false;
+            }
+            if (!mPackageManagerHelper.receiverRegistered(
+                    RulesUpdaterContract.createUpdaterIntent(mUpdateAppPackageName),
+                    RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)) {
+                return false;
+            }
+
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName
+                    + " does not expose the required broadcast receiver.", e);
+            return false;
+        }
+    }
+
+    private static void throwRuntimeExceptionIfNullOrEmpty(String value, String message) {
+        if (value == null || value.trim().isEmpty()) {
+            throw logAndThrowRuntimeException(message, null);
+        }
+    }
+
+    private static RuntimeException logAndThrowRuntimeException(String message, Throwable cause) {
+        Slog.wtf(TAG, message, cause);
+        throw new RuntimeException(message, cause);
+    }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java
new file mode 100644
index 0000000..2e0c21b
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java
@@ -0,0 +1,154 @@
+/*
+ * 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 com.android.internal.R;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.util.List;
+
+/**
+ * A single class that implements multiple helper interfaces for use by {@link PackageTracker}.
+ */
+final class PackageTrackerHelperImpl implements ClockHelper, ConfigHelper, PackageManagerHelper {
+
+    private static final String TAG = "PackageTrackerHelperImpl";
+
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+
+    PackageTrackerHelperImpl(Context context) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Override
+    public boolean isTrackingEnabled() {
+        return mContext.getResources().getBoolean(R.bool.config_timeZoneRulesUpdateTrackingEnabled);
+    }
+
+    @Override
+    public String getUpdateAppPackageName() {
+        return mContext.getResources().getString(R.string.config_timeZoneRulesUpdaterPackage);
+    }
+
+    @Override
+    public String getDataAppPackageName() {
+        Resources resources = mContext.getResources();
+        return resources.getString(R.string.config_timeZoneRulesDataPackage);
+    }
+
+    @Override
+    public int getCheckTimeAllowedMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_timeZoneRulesCheckTimeMillisAllowed);
+    }
+
+    @Override
+    public int getFailedCheckRetryCount() {
+        return mContext.getResources().getInteger(R.integer.config_timeZoneRulesCheckRetryCount);
+    }
+
+    @Override
+    public long currentTimestamp() {
+        // Use of elapsedRealtime() because this is in-memory state and elapsedRealtime() shouldn't
+        // change if the system clock changes.
+        return SystemClock.elapsedRealtime();
+    }
+
+    @Override
+    public int getInstalledPackageVersion(String packageName)
+            throws PackageManager.NameNotFoundException {
+        int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+        PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+        return packageInfo.versionCode;
+    }
+
+    @Override
+    public boolean isPrivilegedApp(String packageName) throws PackageManager.NameNotFoundException {
+        int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+        PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+        return packageInfo.applicationInfo.isPrivilegedApp();
+    }
+
+    @Override
+    public boolean usesPermission(String packageName, String requiredPermissionName)
+            throws PackageManager.NameNotFoundException {
+        int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                | PackageManager.GET_PERMISSIONS;
+        PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+        if (packageInfo.requestedPermissions == null) {
+            return false;
+        }
+        for (String requestedPermission : packageInfo.requestedPermissions) {
+            if (requiredPermissionName.equals(requestedPermission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean contentProviderRegistered(String authority, String requiredPackageName) {
+        int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+        ProviderInfo providerInfo =
+                mPackageManager.resolveContentProvider(authority, flags);
+        if (providerInfo == null) {
+            Slog.i(TAG, "contentProviderRegistered: No content provider registered with authority="
+                    + authority);
+            return false;
+        }
+        boolean packageMatches =
+                requiredPackageName.equals(providerInfo.applicationInfo.packageName);
+        if (!packageMatches) {
+            Slog.i(TAG, "contentProviderRegistered: App with packageName=" + requiredPackageName
+                    + " does not expose the a content provider with authority=" + authority);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean receiverRegistered(Intent intent, String requiredPermissionName)
+            throws PackageManager.NameNotFoundException {
+
+        int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+        List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceivers(intent, flags);
+        if (resolveInfo.size() != 1) {
+            Slog.i(TAG, "receiverRegistered: Zero or multiple broadcast receiver registered for"
+                    + " intent=" + intent + ", found=" + resolveInfo);
+            return false;
+        }
+
+        ResolveInfo matched = resolveInfo.get(0);
+        boolean requiresPermission = requiredPermissionName.equals(matched.activityInfo.permission);
+        if (!requiresPermission) {
+            Slog.i(TAG, "receiverRegistered: Broadcast receiver registered for intent="
+                    + intent + " must require permission " + requiredPermissionName);
+        }
+        return requiresPermission;
+    }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageVersions.java b/services/core/java/com/android/server/timezone/PackageVersions.java
new file mode 100644
index 0000000..fc0d6e1
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageVersions.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+/**
+ * Package version information about the time zone updater and time zone data application packages.
+ */
+final class PackageVersions {
+
+    final int mUpdateAppVersion;
+    final int mDataAppVersion;
+
+    PackageVersions(int updateAppVersion, int dataAppVersion) {
+        this.mUpdateAppVersion = updateAppVersion;
+        this.mDataAppVersion = dataAppVersion;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        PackageVersions that = (PackageVersions) o;
+
+        if (mUpdateAppVersion != that.mUpdateAppVersion) {
+            return false;
+        }
+        return mDataAppVersion == that.mDataAppVersion;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mUpdateAppVersion;
+        result = 31 * result + mDataAppVersion;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "PackageVersions{" +
+                "mUpdateAppVersion=" + mUpdateAppVersion +
+                ", mDataAppVersion=" + mDataAppVersion +
+                '}';
+    }
+}
diff --git a/services/core/java/com/android/server/timezone/PermissionHelper.java b/services/core/java/com/android/server/timezone/PermissionHelper.java
new file mode 100644
index 0000000..ba91c7f
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PermissionHelper.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/**
+ * An easy-to-mock interface around permission checks for use by {@link RulesManagerService}.
+ */
+public interface PermissionHelper {
+
+    void enforceCallerHasPermission(String requiredPermission) throws SecurityException;
+}
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
new file mode 100644
index 0000000..82bd356
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -0,0 +1,348 @@
+/*
+ * 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 com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import android.app.timezone.Callback;
+import android.app.timezone.DistroFormatVersion;
+import android.app.timezone.DistroRulesVersion;
+import android.app.timezone.ICallback;
+import android.app.timezone.IRulesManager;
+import android.app.timezone.RulesManager;
+import android.app.timezone.RulesState;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import libcore.tzdata.shared2.DistroException;
+import libcore.tzdata.shared2.DistroVersion;
+import libcore.tzdata.shared2.StagedDistroOperation;
+import libcore.tzdata.update2.TimeZoneDistroInstaller;
+
+// 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.
+public final class RulesManagerService extends IRulesManager.Stub {
+
+    private static final String TAG = "timezone.RulesManagerService";
+
+    /** The distro format supported by this device. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED =
+            new DistroFormatVersion(
+                    DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+                    DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
+
+    public static class Lifecycle extends SystemService {
+        private RulesManagerService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mService = RulesManagerService.create(getContext());
+            mService.start();
+
+            publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    static final String REQUIRED_UPDATER_PERMISSION =
+            android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
+    private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
+    private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
+
+    private final AtomicBoolean mOperationInProgress = new AtomicBoolean(false);
+    private final PermissionHelper mPermissionHelper;
+    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,
+            TimeZoneDistroInstaller timeZoneDistroInstaller) {
+        mPermissionHelper = permissionHelper;
+        mExecutor = executor;
+        mFileDescriptorHelper = fileDescriptorHelper;
+        mPackageTracker = packageTracker;
+        mInstaller = timeZoneDistroInstaller;
+    }
+
+    public void start() {
+        mPackageTracker.start();
+    }
+
+    @Override // Binder call
+    public RulesState getRulesState() {
+        mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+
+        synchronized(this) {
+            String systemRulesVersion;
+            try {
+                systemRulesVersion = mInstaller.getSystemRulesVersion();
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to read system rules", e);
+                return null;
+            }
+
+            boolean operationInProgress = this.mOperationInProgress.get();
+
+            // Determine the staged operation status, if possible.
+            DistroRulesVersion stagedDistroRulesVersion = null;
+            int stagedOperationStatus = RulesState.STAGED_OPERATION_UNKNOWN;
+            if (!operationInProgress) {
+                StagedDistroOperation stagedDistroOperation;
+                try {
+                    stagedDistroOperation = mInstaller.getStagedDistroOperation();
+                    if (stagedDistroOperation == null) {
+                        stagedOperationStatus = RulesState.STAGED_OPERATION_NONE;
+                    } else if (stagedDistroOperation.isUninstall) {
+                        stagedOperationStatus = RulesState.STAGED_OPERATION_UNINSTALL;
+                    } else {
+                        // Must be an install.
+                        stagedOperationStatus = RulesState.STAGED_OPERATION_INSTALL;
+                        DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion;
+                        stagedDistroRulesVersion = new DistroRulesVersion(
+                                stagedDistroVersion.rulesVersion,
+                                stagedDistroVersion.revision);
+                    }
+                } catch (DistroException | IOException e) {
+                    Slog.w(TAG, "Failed to read staged distro.", e);
+                }
+            }
+
+            // Determine the installed distro state, if possible.
+            DistroVersion installedDistroVersion;
+            int distroStatus = RulesState.DISTRO_STATUS_UNKNOWN;
+            DistroRulesVersion installedDistroRulesVersion = null;
+            if (!operationInProgress) {
+                try {
+                    installedDistroVersion = mInstaller.getInstalledDistroVersion();
+                    if (installedDistroVersion == null) {
+                        distroStatus = RulesState.DISTRO_STATUS_NONE;
+                        installedDistroRulesVersion = null;
+                    } else {
+                        distroStatus = RulesState.DISTRO_STATUS_INSTALLED;
+                        installedDistroRulesVersion = new DistroRulesVersion(
+                                installedDistroVersion.rulesVersion,
+                                installedDistroVersion.revision);
+                    }
+                } catch (DistroException | IOException e) {
+                    Slog.w(TAG, "Failed to read installed distro.", e);
+                }
+            }
+            return new RulesState(systemRulesVersion, DISTRO_FORMAT_VERSION_SUPPORTED,
+                    operationInProgress, stagedOperationStatus, stagedDistroRulesVersion,
+                    distroStatus, installedDistroRulesVersion);
+        }
+    }
+
+    @Override
+    public int requestInstall(
+            ParcelFileDescriptor timeZoneDistro, byte[] checkTokenBytes, ICallback callback) {
+        mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+
+        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);
+
+            // Execute the install asynchronously.
+            mExecutor.execute(new InstallRunnable(timeZoneDistro, checkToken, callback));
+
+            return RulesManager.SUCCESS;
+        }
+    }
+
+    private class InstallRunnable implements Runnable {
+
+        private final ParcelFileDescriptor mTimeZoneDistro;
+        private final CheckToken mCheckToken;
+        private final ICallback mCallback;
+
+        InstallRunnable(
+                ParcelFileDescriptor timeZoneDistro, CheckToken checkToken, ICallback callback) {
+            mTimeZoneDistro = timeZoneDistro;
+            mCheckToken = checkToken;
+            mCallback = callback;
+        }
+
+        @Override
+        public void run() {
+            // 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);
+                int installerResult = mInstaller.stageInstallWithErrorCode(distroBytes);
+                int resultCode = mapInstallerResultToApiCode(installerResult);
+                sendFinishedStatus(mCallback, resultCode);
+
+                // All the installer failure modes are currently non-recoverable and won't be
+                // improved by trying again. Therefore success = true.
+                success = true;
+            } catch (Exception e) {
+                Slog.w(TAG, "Failed to install distro.", e);
+                sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
+            } finally {
+                // Notify the package tracker that the operation is now complete.
+                mPackageTracker.recordCheckResult(mCheckToken, success);
+
+                mOperationInProgress.set(false);
+            }
+        }
+
+        private int mapInstallerResultToApiCode(int installerResult) {
+            switch (installerResult) {
+                case TimeZoneDistroInstaller.INSTALL_SUCCESS:
+                    return Callback.SUCCESS;
+                case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE:
+                    return Callback.ERROR_INSTALL_BAD_DISTRO_STRUCTURE;
+                case TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD:
+                    return Callback.ERROR_INSTALL_RULES_TOO_OLD;
+                case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION:
+                    return Callback.ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION;
+                case TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR:
+                    return Callback.ERROR_INSTALL_VALIDATION_ERROR;
+                default:
+                    return Callback.ERROR_UNKNOWN_FAILURE;
+            }
+        }
+    }
+
+    @Override
+    public int requestUninstall(byte[] checkTokenBytes, ICallback callback) {
+        mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+
+        CheckToken checkToken = null;
+        if (checkTokenBytes != null) {
+            checkToken = createCheckTokenOrThrow(checkTokenBytes);
+        }
+        synchronized(this) {
+            if (callback == null) {
+                throw new NullPointerException("callback == null");
+            }
+
+            if (mOperationInProgress.get()) {
+                return RulesManager.ERROR_OPERATION_IN_PROGRESS;
+            }
+            mOperationInProgress.set(true);
+
+            // Execute the uninstall asynchronously.
+            mExecutor.execute(new UninstallRunnable(checkToken, callback));
+
+            return RulesManager.SUCCESS;
+        }
+    }
+
+    private class UninstallRunnable implements Runnable {
+
+        private final CheckToken mCheckToken;
+        private final ICallback mCallback;
+
+        public UninstallRunnable(CheckToken checkToken, ICallback callback) {
+            mCheckToken = checkToken;
+            mCallback = callback;
+        }
+
+        @Override
+        public void run() {
+            boolean success = false;
+            try {
+                success = mInstaller.stageUninstall();
+                // Right now we just have success (0) / failure (1). All clients should be checking
+                // against SUCCESS. More granular failures may be added in future.
+                int resultCode = success ? Callback.SUCCESS
+                        : Callback.ERROR_UNKNOWN_FAILURE;
+                sendFinishedStatus(mCallback, resultCode);
+            } catch (Exception e) {
+                Slog.w(TAG, "Failed to uninstall distro.", e);
+                sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
+            } finally {
+                // Notify the package tracker that the operation is now complete.
+                mPackageTracker.recordCheckResult(mCheckToken, success);
+
+                mOperationInProgress.set(false);
+            }
+        }
+    }
+
+    private void sendFinishedStatus(ICallback callback, int resultCode) {
+        try {
+            callback.onFinished(resultCode);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to notify observer of result", e);
+        }
+    }
+
+    @Override
+    public void requestNothing(byte[] checkTokenBytes, boolean success) {
+        mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+        CheckToken checkToken = null;
+        if (checkTokenBytes != null) {
+            checkToken = createCheckTokenOrThrow(checkTokenBytes);
+        }
+        mPackageTracker.recordCheckResult(checkToken, success);
+    }
+
+    private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) {
+        CheckToken checkToken;
+        try {
+            checkToken = CheckToken.fromByteArray(checkTokenBytes);
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Unable to read token bytes "
+                    + Arrays.toString(checkTokenBytes), e);
+        }
+        return checkToken;
+    }
+}
diff --git a/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
new file mode 100644
index 0000000..15a571d
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.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 com.android.server.timezone;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+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 {
+
+    private final Context mContext;
+
+    RulesManagerServiceHelperImpl(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void enforceCallerHasPermission(String requiredPermission) {
+        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();
+    }
+
+    @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);
+        }
+    }
+}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 0dbfa56..10ef1be 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -16,6 +16,7 @@
     $(LOCAL_REL_DIR)/com_android_server_am_ActivityManagerService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_AssetAtlasService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
+    $(LOCAL_REL_DIR)/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp \
     $(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_HardwarePropertiesManagerService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecController.cpp \
@@ -58,6 +59,9 @@
     liblog \
     libhardware \
     libhardware_legacy \
+    libhidlbase \
+    libhidltransport \
+    libhwbinder \
     libkeystore_binder \
     libnativehelper \
     libutils \
@@ -73,4 +77,5 @@
     libEGL \
     libGLESv2 \
     libnetutils \
+    android.hardware.tetheroffload.config@1.0 \
 
diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
new file mode 100644
index 0000000..241ccf6
--- /dev/null
+++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <error.h>
+#include <hidl/HidlSupport.h>
+#include <jni.h>
+#include <JNIHelp.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netlink.h>
+#include <sys/socket.h>
+#include <android-base/unique_fd.h>
+#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h>
+
+#define LOG_TAG "OffloadHardwareInterface"
+#include <utils/Log.h>
+
+namespace android {
+
+using hardware::hidl_handle;
+using hardware::hidl_string;
+using hardware::tetheroffload::config::V1_0::IOffloadConfig;
+
+namespace {
+
+inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) {
+    return reinterpret_cast<const sockaddr *>(nladdr);
+}
+
+int conntrackSocket(unsigned groups) {
+    base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER));
+    if (s.get() < 0) return -errno;
+
+    const struct sockaddr_nl bind_addr = {
+        .nl_family = AF_NETLINK,
+        .nl_pad = 0,
+        .nl_pid = 0,
+        .nl_groups = groups,
+    };
+    if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) {
+        return -errno;
+    }
+
+    const struct sockaddr_nl kernel_addr = {
+        .nl_family = AF_NETLINK,
+        .nl_pad = 0,
+        .nl_pid = 0,
+        .nl_groups = groups,
+    };
+    if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) {
+        return -errno;
+    }
+
+    return s.release();
+}
+
+// Return a hidl_handle that owns the file descriptor owned by fd, and will
+// auto-close it (otherwise there would be double-close problems).
+//
+// Rely upon the compiler to eliminate the constexprs used for clarity.
+hidl_handle&& handleFromFileDescriptor(base::unique_fd fd) {
+    hidl_handle h;
+
+    NATIVE_HANDLE_DECLARE_STORAGE(storage, 0, 0);
+    static constexpr int kNumFds = 1;
+    static constexpr int kNumInts = 0;
+    native_handle_t *nh = native_handle_init(storage, kNumFds, kNumInts);
+    nh->data[0] = fd.release();
+
+    static constexpr bool kTakeOwnership = true;
+    h.setTo(nh, kTakeOwnership);
+
+    return std::move(h);
+}
+
+}  // namespace
+
+static jboolean android_server_connectivity_tethering_OffloadHardwareInterface_configOffload(
+        JNIEnv* /* env */) {
+    sp<IOffloadConfig> configInterface = IOffloadConfig::getService();
+    if (configInterface.get() == nullptr) {
+        ALOGD("Could not find IOffloadConfig service.");
+        return false;
+    }
+
+    // Per the IConfigOffload definition:
+    //
+    // fd1   A file descriptor bound to the following netlink groups
+    //       (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
+    //
+    // fd2   A file descriptor bound to the following netlink groups
+    //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
+    base::unique_fd
+            fd1(conntrackSocket(NFNLGRP_CONNTRACK_NEW | NFNLGRP_CONNTRACK_DESTROY)),
+            fd2(conntrackSocket(NFNLGRP_CONNTRACK_UPDATE | NFNLGRP_CONNTRACK_DESTROY));
+    if (fd1.get() < 0 || fd2.get() < 0) {
+        ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
+        return false;
+    }
+
+    hidl_handle h1(handleFromFileDescriptor(std::move(fd1))),
+                h2(handleFromFileDescriptor(std::move(fd2)));
+
+    bool rval;
+    hidl_string msg;
+    configInterface->setHandles(h1, h2,
+            [&rval, &msg](bool success, const hidl_string& errMsg) {
+                rval = success;
+                msg = errMsg;
+            });
+    if (!rval) {
+        ALOGE("IOffloadConfig::setHandles() error: %s", msg.c_str());
+    }
+
+    return rval;
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    { "configOffload", "()Z",
+      (void*) android_server_connectivity_tethering_OffloadHardwareInterface_configOffload },
+};
+
+int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv* env) {
+    return jniRegisterNativeMethods(env,
+            "com/android/server/connectivity/tethering/OffloadHardwareInterface",
+            gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index d3341e5..5d7291a 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -40,6 +40,7 @@
 int register_android_server_location_GnssLocationProvider(JNIEnv* env);
 int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
 int register_android_server_connectivity_Vpn(JNIEnv* env);
+int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv*);
 int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
 int register_android_server_tv_TvUinputBridge(JNIEnv* env);
 int register_android_server_tv_TvInputHal(JNIEnv* env);
@@ -78,6 +79,7 @@
     register_android_server_location_GnssLocationProvider(env);
     register_android_server_location_FlpHardwareProvider(env);
     register_android_server_connectivity_Vpn(env);
+    register_android_server_connectivity_tethering_OffloadHardwareInterface(env);
     register_android_server_AssetAtlasService(env);
     register_android_server_ConsumerIrService(env);
     register_android_server_BatteryStatsService(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b4e806b..e1cbc91 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -171,6 +171,8 @@
             "com.android.server.content.ContentService$Lifecycle";
     private static final String WALLPAPER_SERVICE_CLASS =
             "com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
+    private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
+            "com.android.server.timezone.RulesManagerService$Lifecycle";
 
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
@@ -978,6 +980,13 @@
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
             }
 
+            if (!disableNonCoreServices && context.getResources().getBoolean(
+                        R.bool.config_enableUpdateableTimeZoneRules)) {
+                traceBeginAndSlog("StartTimeZoneRulesManagerService");
+                mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
+                Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+            }
+
             traceBeginAndSlog("StartAudioService");
             mSystemServiceManager.startService(AudioService.Lifecycle.class);
             Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
index b0e0230..703b415 100644
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -46,7 +46,7 @@
     }
 
     public String toString() {
-        return String.format("%s{version: %d, maxSize: %d format: %d}", getClass().getSimpleName(),
+        return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
                 apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
     }
 }
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 0a90749..985a12c 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -18,6 +18,14 @@
 
 import static android.system.OsConstants.*;
 
+import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.internal.util.BitUtils.getUint16;
+import static com.android.internal.util.BitUtils.getUint32;
+import static com.android.internal.util.BitUtils.getUint8;
+import static com.android.internal.util.BitUtils.uint16;
+import static com.android.internal.util.BitUtils.uint32;
+import static com.android.internal.util.BitUtils.uint8;
+
 import android.os.SystemClock;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -1157,41 +1165,9 @@
         }
     }
 
-    private static int uint8(byte b) {
-        return b & 0xff;
-    }
-
-    private static int uint16(short s) {
-        return s & 0xffff;
-    }
-
-    private static long uint32(int i) {
-        return i & 0xffffffffL;
-    }
-
-    private static int getUint8(ByteBuffer buffer, int position) {
-        return uint8(buffer.get(position));
-    }
-
-    private static int getUint16(ByteBuffer buffer, int position) {
-        return uint16(buffer.getShort(position));
-    }
-
-    private static long getUint32(ByteBuffer buffer, int position) {
-        return uint32(buffer.getInt(position));
-    }
-
     // TODO: move to android.net.NetworkUtils
     @VisibleForTesting
     public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
-        return bytesToInt(addrBytes) | (int) (uint32(-1) >>> prefixLength);
-    }
-
-    @VisibleForTesting
-    public static int bytesToInt(byte[] addrBytes) {
-        return (uint8(addrBytes[0]) << 24)
-                + (uint8(addrBytes[1]) << 16)
-                + (uint8(addrBytes[2]) << 8)
-                + (uint8(addrBytes[3]));
+        return bytesToBEInt(addrBytes) | (int) (uint32(-1) >>> prefixLength);
     }
 }
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 745764b..6608167 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -524,7 +524,7 @@
         try {
             mNwService.registerObserver(mNetlinkTracker);
         } catch (RemoteException e) {
-            Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
+            logError("Couldn't register NetlinkTracker: %s", e);
         }
 
         mMultinetworkPolicyTracker.start();
@@ -611,19 +611,36 @@
             return;
         }
 
+        // Thread-unsafe access to mApfFilter but just used for debugging.
+        final ApfFilter apfFilter = mApfFilter;
+        final ProvisioningConfiguration provisioningConfig = mConfiguration;
+        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+                ? provisioningConfig.mApfCapabilities : null;
+
         IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         pw.println(mTag + " APF dump:");
         pw.increaseIndent();
-        // Thread-unsafe access to mApfFilter but just used for debugging.
-        ApfFilter apfFilter = mApfFilter;
         if (apfFilter != null) {
             apfFilter.dump(pw);
         } else {
-            pw.println("No apf support");
+            pw.print("No active ApfFilter; ");
+            if (provisioningConfig == null) {
+                pw.println("IpManager not yet started.");
+            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+                pw.println("Hardware does not support APF.");
+            } else {
+                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+            }
         }
         pw.decreaseIndent();
 
         pw.println();
+        pw.println(mTag + " current ProvisioningConfiguration:");
+        pw.increaseIndent();
+        pw.println(Objects.toString(provisioningConfig, "N/A"));
+        pw.decreaseIndent();
+
+        pw.println();
         pw.println(mTag + " StateMachine dump:");
         pw.increaseIndent();
         mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
@@ -684,7 +701,9 @@
 
     // TODO: Migrate all Log.e(...) to logError(...).
     private void logError(String fmt, Object... args) {
-        mLocalLog.log("ERROR " + String.format(fmt, args));
+        final String msg = "ERROR " + String.format(fmt, args);
+        Log.e(mTag, msg);
+        mLocalLog.log(msg);
     }
 
     private void getNetworkInterface() {
@@ -692,7 +711,7 @@
             mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
         } catch (SocketException | NullPointerException e) {
             // TODO: throw new IllegalStateException.
-            Log.e(mTag, "ALERT: Failed to get interface object: ", e);
+            logError("Failed to get interface object: %s", e);
         }
     }
 
@@ -948,7 +967,7 @@
             ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0"));
             mNwService.setInterfaceConfig(mInterfaceName, ifcg);
         } catch (IllegalStateException | RemoteException e) {
-            Log.e(mTag, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e);
+            logError("Failed to clear IPv4 address on interface %s: %s", mInterfaceName, e);
         }
     }
 
@@ -1074,13 +1093,13 @@
         try {
             mNwService.disableIpv6(mInterfaceName);
         } catch (Exception e) {
-            Log.e(mTag, "Failed to disable IPv6" + e);
+            logError("Failed to disable IPv6: %s", e);
         }
 
         try {
             mNwService.clearInterfaceAddresses(mInterfaceName);
         } catch (Exception e) {
-            Log.e(mTag, "Failed to clear addresses " + e);
+            logError("Failed to clear addresses: %s", e);
         }
     }
 
@@ -1404,7 +1423,7 @@
                     if (setIPv4Address(ipAddress)) {
                         mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
                     } else {
-                        Log.e(mTag, "Failed to set IPv4 address!");
+                        logError("Failed to set IPv4 address.");
                         dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
                                 new LinkProperties(mLinkProperties));
                         transitionTo(mStoppingState);
diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
index 6802cff..25d3329 100644
--- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
+++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
@@ -16,6 +16,8 @@
 
 package android.net.ip;
 
+import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
+import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
 import static android.system.OsConstants.*;
 
 import android.net.IpPrefix;
@@ -68,7 +70,6 @@
     private static final String TAG = RouterAdvertisementDaemon.class.getSimpleName();
     private static final byte ICMPV6_ND_ROUTER_SOLICIT = asByte(133);
     private static final byte ICMPV6_ND_ROUTER_ADVERT  = asByte(134);
-    private static final int IPV6_MIN_MTU = 1280;
     private static final int MIN_RA_HEADER_SIZE = 16;
 
     // Summary of various timers and lifetimes.
@@ -542,6 +543,14 @@
             +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          */
 
+        final HashSet<Inet6Address> filteredDnses = new HashSet<>();
+        for (Inet6Address dns : dnses) {
+            if ((new LinkAddress(dns, RFC7421_PREFIX_LENGTH)).isGlobalPreferred()) {
+                filteredDnses.add(dns);
+            }
+        }
+        if (filteredDnses.isEmpty()) return;
+
         final byte ND_OPTION_RDNSS = 25;
         final byte RDNSS_NUM_8OCTETS = asByte(dnses.size() * 2 + 1);
         ra.put(ND_OPTION_RDNSS)
@@ -549,7 +558,7 @@
           .putShort(asShort(0))
           .putInt(lifetime);
 
-        for (Inet6Address dns : dnses) {
+        for (Inet6Address dns : filteredDnses) {
             // NOTE: If the full of list DNS servers doesn't fit in the packet,
             // this code will cause a buffer overflow and the RA won't include
             // this instance of the option at all.
diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/services/net/java/android/net/util/ConnectivityPacketSummary.java
index 5b068c0..dae93af 100644
--- a/services/net/java/android/net/util/ConnectivityPacketSummary.java
+++ b/services/net/java/android/net/util/ConnectivityPacketSummary.java
@@ -368,9 +368,9 @@
 
         byte[] bytes = new byte[ETHER_ADDR_LEN];
         mac.get(bytes, 0, bytes.length);
-        Byte[] printableBytes = new Byte[bytes.length];
+        Object[] printableBytes = new Object[bytes.length];
         int i = 0;
-        for (byte b : bytes) printableBytes[i++] = b;
+        for (byte b : bytes) printableBytes[i++] = new Byte(b);
 
         final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
         return String.format(MAC48_FORMAT, printableBytes);
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index 26f3050..a012e0c 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -36,6 +36,7 @@
      *
      * See also:
      *     - https://tools.ietf.org/html/rfc894
+     *     - https://tools.ietf.org/html/rfc2464
      *     - https://tools.ietf.org/html/rfc7042
      *     - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
      *     - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
@@ -57,6 +58,8 @@
         FF, FF, FF, FF, FF, FF
     };
 
+    public static final int ETHER_MTU = 1500;
+
     /**
      * ARP constants.
      *
@@ -97,6 +100,7 @@
     public static final int IPV6_SRC_ADDR_OFFSET = 8;
     public static final int IPV6_DST_ADDR_OFFSET = 24;
     public static final int IPV6_ADDR_LEN = 16;
+    public static final int IPV6_MIN_MTU = 1280;
     public static final int RFC7421_PREFIX_LENGTH = 64;
 
     /**
diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java
new file mode 100644
index 0000000..343d237
--- /dev/null
+++ b/services/net/java/android/net/util/SharedLog.java
@@ -0,0 +1,132 @@
+/*
+ * 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.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.StringJoiner;
+
+
+/**
+ * Class to centralize logging functionality for tethering.
+ *
+ * All access to class methods other than dump() must be on the same thread.
+ *
+ * @hide
+ */
+public class SharedLog {
+    private final static int DEFAULT_MAX_RECORDS = 500;
+    private final static String COMPONENT_DELIMITER = ".";
+
+    private enum Category {
+        NONE,
+        ERROR,
+        MARK,
+        WARN,
+    };
+
+    private final LocalLog mLocalLog;
+    // The tag to use for output to the system log. This is not output to the
+    // LocalLog because that would be redundant.
+    private final String mTag;
+    // The component (or subcomponent) of a system that is sharing this log.
+    // This can grow in depth if components call forSubComponent() to obtain
+    // their SharedLog instance. The tag is not included in the component for
+    // brevity.
+    private final String mComponent;
+
+    public SharedLog(String tag) {
+        this(DEFAULT_MAX_RECORDS, tag);
+    }
+
+    public SharedLog(int maxRecords, String tag) {
+        this(new LocalLog(maxRecords), tag, tag);
+    }
+
+    private SharedLog(LocalLog localLog, String tag, String component) {
+        mLocalLog = localLog;
+        mTag = tag;
+        mComponent = component;
+    }
+
+    public SharedLog forSubComponent(String component) {
+        if (!isRootLogInstance()) {
+            component = mComponent + COMPONENT_DELIMITER + component;
+        }
+        return new SharedLog(mLocalLog, mTag, component);
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mLocalLog.readOnlyLocalLog().dump(fd, writer, args);
+    }
+
+    //////
+    // Methods that both log an entry and emit it to the system log.
+    //////
+
+    public void e(Exception e) {
+        Log.e(mTag, record(Category.ERROR, e.toString()));
+    }
+
+    public void e(String msg) {
+        Log.e(mTag, record(Category.ERROR, msg));
+    }
+
+    public void i(String msg) {
+        Log.i(mTag, record(Category.NONE, msg));
+    }
+
+    public void w(String msg) {
+        Log.w(mTag, record(Category.WARN, msg));
+    }
+
+    //////
+    // Methods that only log an entry (and do NOT emit to the system log).
+    //////
+
+    public void log(String msg) {
+        record(Category.NONE, msg);
+    }
+
+    public void mark(String msg) {
+        record(Category.MARK, msg);
+    }
+
+    private String record(Category category, String msg) {
+        final String entry = logLine(category, msg);
+        mLocalLog.log(entry);
+        return entry;
+    }
+
+    private String logLine(Category category, String msg) {
+        final StringJoiner sj = new StringJoiner(" ");
+        if (!isRootLogInstance()) sj.add("[" + mComponent + "]");
+        if (category != Category.NONE) sj.add(category.toString());
+        return sj.add(msg).toString();
+    }
+
+    // Check whether this SharedLog instance is nominally the top level in
+    // a potential hierarchy of shared logs (the root of a tree),
+    // or is a subcomponent within the hierarchy.
+    private boolean isRootLogInstance() {
+        return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/CheckTokenTest.java b/services/tests/servicestests/src/com/android/server/timezone/CheckTokenTest.java
new file mode 100644
index 0000000..9603a06
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/CheckTokenTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.junit.Test;
+
+import android.support.test.filters.SmallTest;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+@SmallTest
+public class CheckTokenTest {
+
+    @Test
+    public void toByteArray() throws Exception {
+        PackageVersions packageVersions =
+                new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+        CheckToken originalToken = new CheckToken(1 /* optimisticLockId */, packageVersions);
+        assertEquals(originalToken, CheckToken.fromByteArray(originalToken.toByteArray()));
+    }
+
+    @Test
+    public void fromByteArray() {
+        PackageVersions packageVersions =
+                new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+        CheckToken token = new CheckToken(1, packageVersions);
+        byte[] validTokenBytes = token.toByteArray();
+        byte[] shortTokenBytes = new byte[validTokenBytes.length - 1];
+        System.arraycopy(validTokenBytes, 0, shortTokenBytes, 0, shortTokenBytes.length);
+
+        try {
+            CheckToken.fromByteArray(shortTokenBytes);
+            fail();
+        } catch (IOException expected) {}
+    }
+
+    @Test
+    public void equals() {
+        PackageVersions packageVersions1 =
+                new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+        PackageVersions packageVersions2 =
+                new PackageVersions(2 /* updateAppVersion */, 2 /* dataAppVersion */);
+        assertFalse(packageVersions1.equals(packageVersions2));
+
+        CheckToken baseline = new CheckToken(1, packageVersions1);
+        assertEquals(baseline, baseline);
+
+        CheckToken deepEqual = new CheckToken(1, packageVersions1);
+        assertEquals(baseline, deepEqual);
+
+        CheckToken differentOptimisticLockId = new CheckToken(2, packageVersions1);
+        assertFalse(differentOptimisticLockId.equals(baseline));
+
+        CheckToken differentPackageVersions = new CheckToken(1, packageVersions2);
+        assertFalse(differentPackageVersions.equals(baseline));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
new file mode 100644
index 0000000..e085270
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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 org.junit.After;
+import org.junit.Before;
+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 static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+@SmallTest
+public class PackageStatusStorageTest {
+    private static final PackageVersions VALID_PACKAGE_VERSIONS = new PackageVersions(1, 2);
+
+    private PackageStatusStorage mPackageStatusStorage;
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+
+        // Using the instrumentation context means the database is created in a test app-specific
+        // directory.
+        mPackageStatusStorage = new PackageStatusStorage(context);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mPackageStatusStorage.deleteDatabaseForTests();
+    }
+
+    @Test
+    public void getPackageStatus_initialState() {
+        assertNull(mPackageStatusStorage.getPackageStatus());
+    }
+
+    @Test
+    public void resetCheckState() {
+        // Assert initial state.
+        assertNull(mPackageStatusStorage.getPackageStatus());
+
+        CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+        // There should now be a state.
+        assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+        // Now clear the state.
+        mPackageStatusStorage.resetCheckState();
+
+        // After reset, there should be no package state again.
+        assertNull(mPackageStatusStorage.getPackageStatus());
+
+        CheckToken token2 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+        // Token after a reset should still be distinct.
+        assertFalse(token1.equals(token2));
+
+        // Now clear the state again.
+        mPackageStatusStorage.resetCheckState();
+
+        // After reset, there should be no package state again.
+        assertNull(mPackageStatusStorage.getPackageStatus());
+
+        CheckToken token3 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+        // A CheckToken generated after a reset should still be distinct.
+        assertFalse(token2.equals(token3));
+    }
+
+    @Test
+    public void generateCheckToken_missingRowBehavior() {
+        // Assert initial state.
+        assertNull(mPackageStatusStorage.getPackageStatus());
+
+        CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+        assertNotNull(token1);
+
+        // There should now be state.
+        assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+        // Corrupt the table by removing the one row.
+        mPackageStatusStorage.deleteRowForTests();
+
+        // Check that generateCheckToken recovers.
+        assertNotNull(mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS));
+    }
+
+    @Test
+    public void getPackageStatus_missingRowBehavior() {
+        // Assert initial state.
+        assertNull(mPackageStatusStorage.getPackageStatus());
+
+        CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+        assertNotNull(token1);
+
+        // There should now be a state.
+        assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+        // Corrupt the table by removing the one row.
+        mPackageStatusStorage.deleteRowForTests();
+
+        assertNull(mPackageStatusStorage.getPackageStatus());
+    }
+
+    @Test
+    public void markChecked_missingRowBehavior() {
+        // Assert initial state.
+        CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+        assertNotNull(token1);
+
+        // There should now be a state.
+        assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+        // Corrupt the table by removing the one row.
+        mPackageStatusStorage.deleteRowForTests();
+
+        // The missing row 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.
+        CheckToken token2 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+        assertTrue(mPackageStatusStorage.markChecked(token2, true /* succeeded */));
+    }
+
+    @Test
+    public void checkToken_tokenIsUnique() {
+        PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+        PackageStatus expectedPackageStatus =
+                new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions);
+
+        CheckToken token1 = mPackageStatusStorage.generateCheckToken(packageVersions);
+        assertEquals(packageVersions, token1.mPackageVersions);
+
+        PackageStatus actualPackageStatus1 = mPackageStatusStorage.getPackageStatus();
+        assertEquals(expectedPackageStatus, actualPackageStatus1);
+
+        CheckToken token2 = mPackageStatusStorage.generateCheckToken(packageVersions);
+        assertEquals(packageVersions, token1.mPackageVersions);
+        assertFalse(token1.mOptimisticLockId == token2.mOptimisticLockId);
+        assertFalse(token1.equals(token2));
+    }
+
+    @Test
+    public void markChecked_checkSucceeded() {
+        PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+
+        CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
+        boolean writeOk = mPackageStatusStorage.markChecked(token, true /* succeeded */);
+        assertTrue(writeOk);
+
+        PackageStatus expectedPackageStatus =
+                new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+        assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+    }
+
+    @Test
+    public void markChecked_checkFailed() {
+        PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+
+        CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
+        boolean writeOk = mPackageStatusStorage.markChecked(token, false /* succeeded */);
+        assertTrue(writeOk);
+
+        PackageStatus expectedPackageStatus =
+                new PackageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, packageVersions);
+        assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+    }
+
+    @Test
+    public void markChecked_optimisticLocking_multipleToken() {
+        PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+        CheckToken token1 = mPackageStatusStorage.generateCheckToken(packageVersions);
+        CheckToken token2 = mPackageStatusStorage.generateCheckToken(packageVersions);
+
+        PackageStatus packageStatusBeforeChecked = mPackageStatusStorage.getPackageStatus();
+
+        boolean writeOk1 = mPackageStatusStorage.markChecked(token1, true /* succeeded */);
+        // Generation of token2 should mean that token1 is no longer valid.
+        assertFalse(writeOk1);
+        assertEquals(packageStatusBeforeChecked, mPackageStatusStorage.getPackageStatus());
+
+        boolean writeOk2 = mPackageStatusStorage.markChecked(token2, true /* succeeded */);
+        // token2 should still be valid, and the attempt with token1 should have had no effect.
+        assertTrue(writeOk2);
+        PackageStatus expectedPackageStatus =
+                new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+        assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+    }
+
+    @Test
+    public void markChecked_optimisticLocking_repeatedTokenUse() {
+        PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+        CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
+
+        boolean writeOk1 = mPackageStatusStorage.markChecked(token, true /* succeeded */);
+        assertTrue(writeOk1);
+
+        PackageStatus expectedPackageStatus =
+                new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+        assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+
+        // token cannot be reused.
+        boolean writeOk2 = mPackageStatusStorage.markChecked(token, true /* succeeded */);
+        assertFalse(writeOk2);
+        assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusTest.java
new file mode 100644
index 0000000..c0ae81e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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 org.junit.Test;
+
+import android.support.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@SmallTest
+public class PackageStatusTest {
+
+    @Test
+    public void equals() {
+        PackageVersions packageVersions1 =
+                new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+        PackageVersions packageVersions2 =
+                new PackageVersions(2 /* updateAppVersion */, 1 /* dataAppVersion */);
+        assertFalse(packageVersions1.equals(packageVersions2));
+
+        PackageStatus baseline =
+                new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions1);
+        assertEquals(baseline, baseline);
+
+        PackageStatus deepEqual =
+                new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions1);
+        assertEquals(baseline, deepEqual);
+
+        PackageStatus differentStatus =
+                new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions1);
+        assertFalse(differentStatus.equals(baseline));
+
+        PackageStatus differentPackageVersions =
+                new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions2);
+        assertFalse(differentPackageVersions.equals(baseline));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
new file mode 100644
index 0000000..45b0af3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
@@ -0,0 +1,1471 @@
+/*
+ * 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 org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import android.app.timezone.RulesUpdaterContract;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.TimeZoneRulesDataContract;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+/**
+ * White box interaction / unit testing of the {@link PackageTracker}.
+ */
+@SmallTest
+public class PackageTrackerTest {
+    private static final String UPDATE_APP_PACKAGE_NAME = "updateAppPackageName";
+    private static final String DATA_APP_PACKAGE_NAME = "dataAppPackageName";
+    private static final PackageVersions INITIAL_APP_PACKAGE_VERSIONS =
+            new PackageVersions(2 /* updateAppVersion */, 2 /* dataAppVersion */);
+
+    private ConfigHelper mMockConfigHelper;
+    private PackageManagerHelper mMockPackageManagerHelper;
+
+    private FakeClockHelper mFakeClock;
+    private FakeIntentHelper mFakeIntentHelper;
+    private PackageStatusStorage mPackageStatusStorage;
+    private PackageTracker mPackageTracker;
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+
+        mFakeClock = new FakeClockHelper();
+
+        // Read-only interfaces so are easy to mock.
+        mMockConfigHelper = mock(ConfigHelper.class);
+        mMockPackageManagerHelper = mock(PackageManagerHelper.class);
+
+        // 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);
+
+        // For other interactions with the Android framework we create a fake object.
+        mFakeIntentHelper = new FakeIntentHelper();
+
+        // Create the PackageTracker to use in tests.
+        mPackageTracker = new PackageTracker(
+                mFakeClock,
+                mMockConfigHelper,
+                mMockPackageManagerHelper,
+                mPackageStatusStorage,
+                mFakeIntentHelper);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mPackageStatusStorage != null) {
+            mPackageStatusStorage.deleteDatabaseForTests();
+        }
+    }
+
+    @Test
+    public void trackingDisabled_intentHelperNotUsed() {
+        // Set up device configuration.
+        configureTrackingDisabled();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check the IntentHelper was not initialized.
+        mFakeIntentHelper.assertNotInitialized();
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+    }
+
+    @Test
+    public void trackingDisabled_triggerUpdateIfNeededNotAllowed() {
+        // Set up device configuration.
+        configureTrackingDisabled();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+        try {
+            // This call should also not be allowed and will throw an exception if tracking is
+            // disabled.
+            mPackageTracker.triggerUpdateIfNeeded(true);
+            fail();
+        } catch (IllegalStateException expected) {}
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+    }
+
+    @Test
+    public void trackingDisabled_unsolicitedResultsIgnored_withoutToken() {
+        // Set up device configuration.
+        configureTrackingDisabled();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+        // Receiving a check result when tracking is disabled should cause the storage to be
+        // reset.
+        mPackageTracker.recordCheckResult(null /* checkToken */, true /* success */);
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+        // Assert the storage was reset.
+        checkPackageStorageStatusIsInitialOrReset();
+    }
+
+    @Test
+    public void trackingDisabled_unsolicitedResultsIgnored_withToken() {
+        // Set up device configuration.
+        configureTrackingDisabled();
+
+        // Set the storage into an arbitrary state so we can detect a reset.
+        mPackageStatusStorage.generateCheckToken(INITIAL_APP_PACKAGE_VERSIONS);
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+        // Receiving a check result when tracking is disabled should cause the storage to be reset.
+        mPackageTracker.recordCheckResult(createArbitraryCheckToken(), true /* success */);
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+        // Assert the storage was reset.
+        checkPackageStorageStatusIsInitialOrReset();
+    }
+
+    @Test
+    public void trackingEnabled_updateAppConfigMissing() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureUpdateAppPackageNameMissing();
+        configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
+
+        try {
+            // Initialize the tracker.
+            mPackageTracker.start();
+            fail();
+        } catch (RuntimeException expected) {}
+
+        mFakeIntentHelper.assertNotInitialized();
+
+        // Check reliability triggering state.
+        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_dataAppConfigMissing() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
+        configureDataAppPackageNameMissing();
+
+        try {
+            // Initialize the tracker.
+            mPackageTracker.start();
+            fail();
+        } catch (RuntimeException expected) {}
+
+        mFakeIntentHelper.assertNotInitialized();
+
+        // Check reliability triggering state.
+        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_packageUpdate_badUpdateAppManifestEntry() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Configure a bad manifest for the update app. Should effectively turn off tracking.
+        PackageVersions packageVersions =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        configureUpdateAppManifestBad(UPDATE_APP_PACKAGE_NAME);
+        configureDataAppManifestOk(DATA_APP_PACKAGE_NAME);
+        configureUpdateAppPackageVersion(
+                UPDATE_APP_PACKAGE_NAME, packageVersions.mUpdateAppVersion);
+        configureDataAppPackageVersion(DATA_APP_PACKAGE_NAME, packageVersions.mDataAppVersion);
+        // Simulate a tracked package being updated.
+        mFakeIntentHelper.simulatePackageUpdatedEvent();
+
+        // Assert the PackageTracker did not attempt to trigger an update.
+        mFakeIntentHelper.assertUpdateNotTriggered();
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+        // Assert the storage was not touched.
+        checkPackageStorageStatusIsInitialOrReset();
+    }
+
+    @Test
+    public void trackingEnabled_packageUpdate_badDataAppManifestEntry() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Configure a bad manifest for the data app. Should effectively turn off tracking.
+        PackageVersions packageVersions =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        configureUpdateAppManifestOk(UPDATE_APP_PACKAGE_NAME);
+        configureDataAppManifestBad(DATA_APP_PACKAGE_NAME);
+        configureUpdateAppPackageVersion(
+                UPDATE_APP_PACKAGE_NAME, packageVersions.mUpdateAppVersion);
+        configureDataAppPackageVersion(DATA_APP_PACKAGE_NAME, packageVersions.mDataAppVersion);
+        mFakeIntentHelper.simulatePackageUpdatedEvent();
+
+        // Assert the PackageTracker did not attempt to trigger an update.
+        mFakeIntentHelper.assertUpdateNotTriggered();
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+        // Assert the storage was not touched.
+        checkPackageStorageStatusIsInitialOrReset();
+    }
+
+    @Test
+    public void trackingEnabled_packageUpdate_responseWithToken_success() throws Exception {
+        trackingEnabled_packageUpdate_responseWithToken(true);
+    }
+
+    @Test
+    public void trackingEnabled_packageUpdate_responseWithToken_failed() throws Exception {
+        trackingEnabled_packageUpdate_responseWithToken(false);
+    }
+
+    private void trackingEnabled_packageUpdate_responseWithToken(boolean success) throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Simulate a tracked package being updated.
+        PackageVersions packageVersions =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions);
+
+        // Get the token that was passed to the intent helper, and pass it back.
+        CheckToken token = mFakeIntentHelper.captureAndResetLastToken();
+        mPackageTracker.recordCheckResult(token, success);
+
+        // Check storage and reliability triggering state.
+        if (success) {
+            checkUpdateCheckSuccessful(packageVersions);
+        } else {
+            checkUpdateCheckFailed(packageVersions);
+        }
+    }
+
+    @Test
+    public void trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset_success()
+            throws Exception {
+        trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset(true);
+    }
+
+    @Test
+    public void trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset_failed()
+            throws Exception {
+        trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset(false);
+    }
+
+    private void trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset(
+            boolean success) throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Set up installed app versions / manifests.
+        PackageVersions packageVersions =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions);
+
+        // Ignore the token that was given to the intent helper, just pass null.
+        mPackageTracker.recordCheckResult(null /* checkToken */, success);
+
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+        // Assert the storage was reset.
+        checkPackageStorageStatusIsInitialOrReset();
+    }
+
+    /**
+     * Two package updates triggered for the same package versions. The second is triggered while
+     * the first is still happening.
+     */
+    @Test
+    public void trackingEnabled_packageUpdate_twoChecksNoPackageChange_secondWhileFirstInProgress()
+            throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Simulate package installation.
+        PackageVersions packageVersions =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions);
+
+        // Get the first token.
+        CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions, token1.mPackageVersions);
+
+        // Now attempt to generate another check while the first is in progress and without having
+        // updated the package versions. The PackageTracker should trigger again for safety.
+        simulatePackageInstallation(packageVersions);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions);
+
+        CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions, token2.mPackageVersions);
+        assertEquals(token1.mPackageVersions, token2.mPackageVersions);
+        assertTrue(token1.mOptimisticLockId != token2.mOptimisticLockId);
+    }
+
+    /**
+     * Two package updates triggered for the same package versions. The second happens after
+     * the first has succeeded.
+     */
+    @Test
+    public void trackingEnabled_packageUpdate_twoChecksNoPackageChange_sequential()
+            throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Simulate package installation.
+        PackageVersions packageVersions =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions);
+
+        // Get the token.
+        CheckToken token = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions, token.mPackageVersions);
+
+        // Simulate a successful check.
+        mPackageTracker.recordCheckResult(token, true /* success */);
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(packageVersions);
+
+        // Now attempt to generate another check, but without having updated the package. The
+        // PackageTracker should be smart enough to recognize there's nothing to do here.
+        simulatePackageInstallation(packageVersions);
+
+        // Assert the PackageTracker did not attempt to trigger an update.
+        mFakeIntentHelper.assertUpdateNotTriggered();
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(packageVersions);
+    }
+
+    /**
+     * Two package updates triggered for the same package versions. The second is triggered after
+     * the first has failed.
+     */
+    @Test
+    public void trackingEnabled_packageUpdate_afterFailure() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Simulate package installation.
+        PackageVersions packageVersions =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions);
+
+        // Get the first token.
+        CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions, token1.mPackageVersions);
+
+        // Simulate an *unsuccessful* check.
+        mPackageTracker.recordCheckResult(token1, false /* success */);
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckFailed(packageVersions);
+
+        // Now generate another check, but without having updated the package. The
+        // PackageTracker should recognize the last check failed and trigger again.
+        simulatePackageInstallation(packageVersions);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions);
+
+        // Get the second token.
+        CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+        // Assert some things about the tokens.
+        assertEquals(packageVersions, token2.mPackageVersions);
+        assertTrue(token1.mOptimisticLockId != token2.mOptimisticLockId);
+
+        // For completeness, now simulate this check was successful.
+        mPackageTracker.recordCheckResult(token2, true /* success */);
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(packageVersions);
+    }
+
+    /**
+     * Two package updates triggered for different package versions. The second is triggered while
+     * the first is still happening.
+     */
+    @Test
+    public void trackingEnabled_packageUpdate_twoChecksWithPackageChange_firstCheckInProcess()
+            throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Simulate package installation.
+        PackageVersions packageVersions1 =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions1);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions1);
+
+        // Get the first token.
+        CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions1, token1.mPackageVersions);
+
+        // Simulate a tracked package being updated a second time (before the response for the
+        // first has been received).
+        PackageVersions packageVersions2 =
+                new PackageVersions(3 /* updateAppPackageVersion */, 4 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions2);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions2);
+
+        // Get the second token.
+        CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions2, token2.mPackageVersions);
+
+        // token1 should be invalid because the token2 was generated.
+        mPackageTracker.recordCheckResult(token1, true /* success */);
+
+        // Reliability triggering should still be enabled.
+        mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+        // Check the expected storage state.
+        checkPackageStorageStatus(PackageStatus.CHECK_STARTED, packageVersions2);
+
+        // token2 should still be accepted.
+        mPackageTracker.recordCheckResult(token2, true /* success */);
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(packageVersions2);
+    }
+
+    /**
+     * Two package updates triggered for different package versions. The second is triggered after
+     * the first has completed successfully.
+     */
+    @Test
+    public void trackingEnabled_packageUpdate_twoChecksWithPackageChange_sequential()
+            throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Simulate package installation.
+        PackageVersions packageVersions1 =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions1);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions1);
+
+        // Get the first token.
+        CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions1, token1.mPackageVersions);
+
+        // token1 should be accepted.
+        mPackageTracker.recordCheckResult(token1, true /* success */);
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(packageVersions1);
+
+        // Simulate a tracked package being updated a second time.
+        PackageVersions packageVersions2 =
+                new PackageVersions(3 /* updateAppPackageVersion */, 4 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions2);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions2);
+
+        // Get the second token.
+        CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions2, token2.mPackageVersions);
+
+        // token2 should still be accepted.
+        mPackageTracker.recordCheckResult(token2, true /* success */);
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(packageVersions2);
+    }
+
+    /**
+     * Replaying the same token twice.
+     */
+    @Test
+    public void trackingEnabled_packageUpdate_sameTokenReplayFails() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        configureValidApplications();
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Simulate package installation.
+        PackageVersions packageVersions1 =
+                new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+        simulatePackageInstallation(packageVersions1);
+
+        // Confirm an update was triggered.
+        checkUpdateCheckTriggered(packageVersions1);
+
+        // Get the first token.
+        CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions1, token1.mPackageVersions);
+
+        // token1 should be accepted.
+        mPackageTracker.recordCheckResult(token1, true /* success */);
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(packageVersions1);
+
+        // Apply token1 again.
+        mPackageTracker.recordCheckResult(token1, true /* success */);
+
+        // Check the expected storage state. No real way to tell if it has been updated, but
+        // we can check the final state is still what it should be.
+        checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions1);
+
+        // Under the covers we expect it to fail to update because the storage should recognize that
+        // the token is no longer valid.
+        mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+        // Peek inside the package tracker to make sure it is tracking failure counts properly.
+        assertEquals(1, mPackageTracker.getCheckFailureCountForTests());
+    }
+
+    @Test
+    public void trackingEnabled_reliabilityTrigger_firstTime_initialStorage() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        PackageVersions packageVersions = configureValidApplications();
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatusIsInitialOrReset();
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Assert the PackageTracker did trigger an update.
+        checkUpdateCheckTriggered(packageVersions);
+
+        // Confirm the token was correct.
+        CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+        assertEquals(packageVersions, token1.mPackageVersions);
+
+        // token1 should be accepted.
+        mPackageTracker.recordCheckResult(token1, true /* success */);
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(packageVersions);
+    }
+
+    @Test
+    public void trackingEnabled_reliabilityTrigger_afterRebootNoTriggerNeeded() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+        PackageVersions packageVersions = configureValidApplications();
+
+        // Force the storage into a state we want.
+        mPackageStatusStorage.forceCheckStateForTests(
+                PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Assert the PackageTracker did not attempt to trigger an update.
+        mFakeIntentHelper.assertUpdateNotTriggered();
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(packageVersions);
+    }
+
+    /**
+     * Simulates the device starting where the storage records do not match the installed app
+     * versions. The reliability trigger should cause the package tracker to perform a check.
+     */
+    @Test
+    public void trackingEnabled_reliabilityTrigger_afterRebootTriggerNeededBecausePreviousFailed()
+            throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+        configureReliabilityConfigSettingsOk();
+
+        PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+        PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+        // Simulate there being a newer version installed than the one recorded in storage.
+        configureValidApplications(currentPackageVersions);
+
+        // Force the storage into a state we want.
+        mPackageStatusStorage.forceCheckStateForTests(
+                PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Assert the PackageTracker did trigger an update.
+        checkUpdateCheckTriggered(currentPackageVersions);
+
+        // Simulate the update check completing successfully.
+        CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+        mPackageTracker.recordCheckResult(checkToken, true /* success */);
+
+        // Check storage and reliability triggering state.
+        checkUpdateCheckSuccessful(currentPackageVersions);
+    }
+
+    /**
+     * Simulates persistent failures of the reliability check. It should stop after the configured
+     * number of checks.
+     */
+    @Test
+    public void trackingEnabled_reliabilityTrigger_repeatedFailures() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+
+        int retriesAllowed = 3;
+        int checkDelayMillis = 5 * 60 * 1000;
+        configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+        PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+        PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+        // Simulate there being a newer version installed than the one recorded in storage.
+        configureValidApplications(currentPackageVersions);
+
+        // Force the storage into a state we want.
+        mPackageStatusStorage.forceCheckStateForTests(
+                PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+        for (int i = 0; i < retriesAllowed + 1; i++) {
+            // Simulate a reliability trigger.
+            mFakeIntentHelper.simulateReliabilityTrigger();
+
+            // Assert the PackageTracker did trigger an update.
+            checkUpdateCheckTriggered(currentPackageVersions);
+
+            // Check the PackageTracker failure count before calling recordCheckResult.
+            assertEquals(i, mPackageTracker.getCheckFailureCountForTests());
+
+            // Simulate a check failure.
+            CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+            mPackageTracker.recordCheckResult(checkToken, false /* success */);
+
+            // Peek inside the package tracker to make sure it is tracking failure counts properly.
+            assertEquals(i + 1, mPackageTracker.getCheckFailureCountForTests());
+
+            // Confirm nothing has changed.
+            mFakeIntentHelper.assertUpdateNotTriggered();
+            checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE,
+                    currentPackageVersions);
+
+            // Check reliability triggering is in the correct state.
+            if (i <= retriesAllowed) {
+                mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+            } else {
+                mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+            }
+        }
+    }
+
+    @Test
+    public void trackingEnabled_reliabilityTrigger_failureCountIsReset() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+
+        int retriesAllowed = 3;
+        int checkDelayMillis = 5 * 60 * 1000;
+        configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+        PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+        PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+        // Simulate there being a newer version installed than the one recorded in storage.
+        configureValidApplications(currentPackageVersions);
+
+        // Force the storage into a state we want.
+        mPackageStatusStorage.forceCheckStateForTests(
+                PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+        // Fail (retries - 1) times.
+        for (int i = 0; i < retriesAllowed - 1; i++) {
+            // Simulate a reliability trigger.
+            mFakeIntentHelper.simulateReliabilityTrigger();
+
+            // Assert the PackageTracker did trigger an update.
+            checkUpdateCheckTriggered(currentPackageVersions);
+
+            // Check the PackageTracker failure count before calling recordCheckResult.
+            assertEquals(i, mPackageTracker.getCheckFailureCountForTests());
+
+            // Simulate a check failure.
+            CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+            mPackageTracker.recordCheckResult(checkToken, false /* success */);
+
+            // Peek inside the package tracker to make sure it is tracking failure counts properly.
+            assertEquals(i + 1, mPackageTracker.getCheckFailureCountForTests());
+
+            // Confirm nothing has changed.
+            mFakeIntentHelper.assertUpdateNotTriggered();
+            checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE,
+                    currentPackageVersions);
+
+            // Check reliability triggering is still enabled.
+            mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+        }
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Assert the PackageTracker did trigger an update.
+        checkUpdateCheckTriggered(currentPackageVersions);
+
+        // Check the PackageTracker failure count before calling recordCheckResult.
+        assertEquals(retriesAllowed - 1, mPackageTracker.getCheckFailureCountForTests());
+
+        // On the last possible try, succeed.
+        CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+        mPackageTracker.recordCheckResult(checkToken, true /* success */);
+
+        checkUpdateCheckSuccessful(currentPackageVersions);
+    }
+
+    /**
+     * Simulates reliability triggers happening too close together. Package tracker should ignore
+     * the ones it doesn't need.
+     */
+    @Test
+    public void trackingEnabled_reliabilityTrigger_tooSoon() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+
+        int retriesAllowed = 5;
+        int checkDelayMillis = 5 * 60 * 1000;
+        configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+        PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+        PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+        // Simulate there being a newer version installed than the one recorded in storage.
+        configureValidApplications(currentPackageVersions);
+
+        // Force the storage into a state we want.
+        mPackageStatusStorage.forceCheckStateForTests(
+                PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Assert the PackageTracker did trigger an update.
+        checkUpdateCheckTriggered(currentPackageVersions);
+        CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+
+        // Increment the clock, but not enough.
+        mFakeClock.incrementClock(checkDelayMillis - 1);
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Assert the PackageTracker did not trigger an update.
+        mFakeIntentHelper.assertUpdateNotTriggered();
+        checkPackageStorageStatus(PackageStatus.CHECK_STARTED, currentPackageVersions);
+        mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+        // Increment the clock slightly more. Should now consider the response overdue.
+        mFakeClock.incrementClock(2);
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Triggering should have happened.
+        checkUpdateCheckTriggered(currentPackageVersions);
+        CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+        // Check a new token was generated.
+        assertFalse(token1.equals(token2));
+    }
+
+    /**
+     * Tests what happens when a package update doesn't complete and a reliability trigger cleans
+     * up for it.
+     */
+    @Test
+    public void trackingEnabled_reliabilityTrigger_afterPackageUpdateDidNotComplete()
+            throws Exception {
+
+        // Set up device configuration.
+        configureTrackingEnabled();
+
+        int retriesAllowed = 5;
+        int checkDelayMillis = 5 * 60 * 1000;
+        configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+        PackageVersions currentPackageVersions = new PackageVersions(1, 1);
+        PackageVersions newPackageVersions = new PackageVersions(2, 2);
+
+        // Simulate there being a newer version installed than the one recorded in storage.
+        configureValidApplications(currentPackageVersions);
+
+        // Force the storage into a state we want.
+        mPackageStatusStorage.forceCheckStateForTests(
+                PackageStatus.CHECK_COMPLETED_SUCCESS, currentPackageVersions);
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Simulate a reliability trigger.
+        simulatePackageInstallation(newPackageVersions);
+
+        // Assert the PackageTracker did trigger an update.
+        checkUpdateCheckTriggered(newPackageVersions);
+        CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+
+        // Increment the clock, but not enough.
+        mFakeClock.incrementClock(checkDelayMillis + 1);
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Assert the PackageTracker triggered an update.
+        checkUpdateCheckTriggered(newPackageVersions);
+        CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+        // Check a new token was generated.
+        assertFalse(token1.equals(token2));
+
+        // Simulate the reliability check completing.
+        mPackageTracker.recordCheckResult(token2, true /* success */);
+
+        // Check everything is now as it should be.
+        checkUpdateCheckSuccessful(newPackageVersions);
+    }
+
+    /**
+     * Simulates a reliability trigger happening too soon after a package update trigger occurred.
+     */
+    @Test
+    public void trackingEnabled_reliabilityTriggerAfterUpdate_tooSoon() throws Exception {
+        // Set up device configuration.
+        configureTrackingEnabled();
+
+        int retriesAllowed = 5;
+        int checkDelayMillis = 5 * 60 * 1000;
+        configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+        PackageVersions currentPackageVersions = new PackageVersions(1, 1);
+        PackageVersions newPackageVersions = new PackageVersions(2, 2);
+
+        // Simulate there being a newer version installed than the one recorded in storage.
+        configureValidApplications(currentPackageVersions);
+
+        // Force the storage into a state we want.
+        mPackageStatusStorage.forceCheckStateForTests(
+                PackageStatus.CHECK_COMPLETED_SUCCESS, currentPackageVersions);
+
+        // Initialize the package tracker.
+        mPackageTracker.start();
+
+        // Check the intent helper is properly configured.
+        checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+        // Check the initial storage state.
+        checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, currentPackageVersions);
+
+        // Simulate a package update trigger.
+        simulatePackageInstallation(newPackageVersions);
+
+        // Assert the PackageTracker did trigger an update.
+        checkUpdateCheckTriggered(newPackageVersions);
+        CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+
+        // Increment the clock, but not enough.
+        mFakeClock.incrementClock(checkDelayMillis - 1);
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Assert the PackageTracker did not trigger an update.
+        mFakeIntentHelper.assertUpdateNotTriggered();
+        checkPackageStorageStatus(PackageStatus.CHECK_STARTED, newPackageVersions);
+        mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+        // Increment the clock slightly more. Should now consider the response overdue.
+        mFakeClock.incrementClock(2);
+
+        // Simulate a reliability trigger.
+        mFakeIntentHelper.simulateReliabilityTrigger();
+
+        // Triggering should have happened.
+        checkUpdateCheckTriggered(newPackageVersions);
+        CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+        // Check a new token was generated.
+        assertFalse(token1.equals(token2));
+    }
+
+    private void simulatePackageInstallation(PackageVersions packageVersions) throws Exception {
+        configureApplicationsValidManifests(packageVersions);
+
+        // Simulate a tracked package being updated.
+        mFakeIntentHelper.simulatePackageUpdatedEvent();
+    }
+
+    /**
+     * Checks an update check was triggered, reliability triggering is therefore enabled and the
+     * storage state reflects that there is a check in progress.
+     */
+    private void checkUpdateCheckTriggered(PackageVersions packageVersions) {
+        // Assert the PackageTracker attempted to trigger an update.
+        mFakeIntentHelper.assertUpdateTriggered();
+
+        // If an update check was triggered reliability triggering should always be enabled to
+        // ensure that it can be completed if it fails.
+        mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+        // Check the expected storage state.
+        checkPackageStorageStatus(PackageStatus.CHECK_STARTED, packageVersions);
+    }
+
+    private void checkUpdateCheckFailed(PackageVersions packageVersions) {
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+        // Assert the storage was updated.
+        checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, packageVersions);
+    }
+
+    private void checkUpdateCheckSuccessful(PackageVersions packageVersions) {
+        // Check reliability triggering state.
+        mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+        // Assert the storage was updated.
+        checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+
+        // Peek inside the package tracker to make sure it is tracking failure counts properly.
+        assertEquals(0, mPackageTracker.getCheckFailureCountForTests());
+    }
+
+    private PackageVersions configureValidApplications() throws Exception {
+        configureValidApplications(INITIAL_APP_PACKAGE_VERSIONS);
+        return INITIAL_APP_PACKAGE_VERSIONS;
+    }
+
+    private void configureValidApplications(PackageVersions versions) throws Exception {
+        configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
+        configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
+        configureApplicationsValidManifests(versions);
+    }
+
+    private void configureApplicationsValidManifests(PackageVersions versions) throws Exception {
+        configureUpdateAppManifestOk(UPDATE_APP_PACKAGE_NAME);
+        configureDataAppManifestOk(DATA_APP_PACKAGE_NAME);
+        configureUpdateAppPackageVersion(UPDATE_APP_PACKAGE_NAME, versions.mUpdateAppVersion);
+        configureDataAppPackageVersion(DATA_APP_PACKAGE_NAME, versions.mDataAppVersion);
+    }
+
+    private void configureUpdateAppPackageVersion(String updateAppPackageName,
+            int updataAppPackageVersion) throws Exception {
+        when(mMockPackageManagerHelper.getInstalledPackageVersion(updateAppPackageName))
+                .thenReturn(updataAppPackageVersion);
+    }
+
+    private void configureDataAppPackageVersion(String dataAppPackageName,
+            int dataAppPackageVersion) throws Exception {
+        when(mMockPackageManagerHelper.getInstalledPackageVersion(dataAppPackageName))
+                .thenReturn(dataAppPackageVersion);
+    }
+
+    private void configureUpdateAppManifestOk(String updateAppPackageName) throws Exception {
+        Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(updateAppPackageName);
+        when(mMockPackageManagerHelper.receiverRegistered(
+                filterEquals(expectedIntent),
+                eq(RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)))
+                .thenReturn(true);
+        when(mMockPackageManagerHelper.usesPermission(
+                updateAppPackageName, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION))
+                .thenReturn(true);
+    }
+
+    private void configureUpdateAppManifestBad(String updateAppPackageName) throws Exception {
+        Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(updateAppPackageName);
+        when(mMockPackageManagerHelper.receiverRegistered(
+                filterEquals(expectedIntent),
+                eq(RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)))
+                .thenReturn(false);
+        // Has permission, but that shouldn't matter if the check above is false.
+        when(mMockPackageManagerHelper.usesPermission(
+                updateAppPackageName, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION))
+                .thenReturn(true);
+    }
+
+    private void configureDataAppManifestOk(String dataAppPackageName) throws Exception {
+        when(mMockPackageManagerHelper.contentProviderRegistered(
+                TimeZoneRulesDataContract.AUTHORITY, dataAppPackageName))
+                .thenReturn(true);
+    }
+
+    private void configureDataAppManifestBad(String dataAppPackageName) throws Exception {
+        // Simulate the data app not exposing the content provider we require.
+        when(mMockPackageManagerHelper.contentProviderRegistered(
+                TimeZoneRulesDataContract.AUTHORITY, dataAppPackageName))
+                .thenReturn(false);
+    }
+
+    private void configureTrackingEnabled() {
+        when(mMockConfigHelper.isTrackingEnabled()).thenReturn(true);
+    }
+
+    private void configureTrackingDisabled() {
+        when(mMockConfigHelper.isTrackingEnabled()).thenReturn(false);
+    }
+
+    private void configureReliabilityConfigSettings(int retriesAllowed, int checkDelayMillis) {
+        when(mMockConfigHelper.getFailedCheckRetryCount()).thenReturn(retriesAllowed);
+        when(mMockConfigHelper.getCheckTimeAllowedMillis()).thenReturn(checkDelayMillis);
+    }
+
+    private void configureReliabilityConfigSettingsOk() {
+        configureReliabilityConfigSettings(5, 5 * 60 * 1000);
+    }
+
+    private void configureUpdateAppPackageOk(String updateAppPackageName) throws Exception {
+        when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(updateAppPackageName);
+        when(mMockPackageManagerHelper.isPrivilegedApp(updateAppPackageName)).thenReturn(true);
+    }
+
+    private void configureUpdateAppPackageNotPrivileged(String updateAppPackageName)
+            throws Exception {
+        when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(updateAppPackageName);
+        when(mMockPackageManagerHelper.isPrivilegedApp(updateAppPackageName)).thenReturn(false);
+    }
+
+    private void configureUpdateAppPackageNameMissing() {
+        when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(null);
+    }
+
+    private void configureDataAppPackageOk(String dataAppPackageName) throws Exception {
+        when(mMockConfigHelper.getDataAppPackageName()).thenReturn(dataAppPackageName);
+        when(mMockPackageManagerHelper.isPrivilegedApp(dataAppPackageName)).thenReturn(true);
+    }
+
+    private void configureDataAppPackageNotPrivileged(String dataAppPackageName)
+            throws Exception {
+        when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(dataAppPackageName);
+        when(mMockPackageManagerHelper.isPrivilegedApp(dataAppPackageName)).thenReturn(false);
+    }
+
+    private void configureDataAppPackageNameMissing() {
+        when(mMockConfigHelper.getDataAppPackageName()).thenThrow(new RuntimeException());
+    }
+
+    private void checkIntentHelperInitializedAndReliabilityTrackingEnabled() {
+        // Verify that calling start initialized the IntentHelper as well.
+        mFakeIntentHelper.assertInitialized(UPDATE_APP_PACKAGE_NAME, DATA_APP_PACKAGE_NAME);
+
+        // Assert that reliability tracking is always enabled after initialization.
+        mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+    }
+
+    private void checkPackageStorageStatus(
+            int expectedCheckStatus, PackageVersions expectedPackageVersions) {
+        PackageStatus packageStatus = mPackageStatusStorage.getPackageStatus();
+        assertEquals(expectedCheckStatus, packageStatus.mCheckStatus);
+        assertEquals(expectedPackageVersions, packageStatus.mVersions);
+    }
+
+    private void checkPackageStorageStatusIsInitialOrReset() {
+        assertNull(mPackageStatusStorage.getPackageStatus());
+    }
+
+    private static CheckToken createArbitraryCheckToken() {
+        return new CheckToken(1, INITIAL_APP_PACKAGE_VERSIONS);
+    }
+
+    /**
+     * A fake IntentHelper implementation for use in tests.
+     */
+    private static class FakeIntentHelper implements IntentHelper {
+
+        private Listener mListener;
+        private String mUpdateAppPackageName;
+        private String mDataAppPackageName;
+
+        private CheckToken mLastToken;
+
+        private boolean mReliabilityTriggeringEnabled;
+
+        @Override
+        public void initialize(String updateAppPackageName, String dataAppPackageName,
+                Listener listener) {
+            assertNotNull(updateAppPackageName);
+            assertNotNull(dataAppPackageName);
+            assertNotNull(listener);
+            mListener = listener;
+            mUpdateAppPackageName = updateAppPackageName;
+            mDataAppPackageName = dataAppPackageName;
+        }
+
+        public void assertInitialized(
+                String expectedUpdateAppPackageName, String expectedDataAppPackageName) {
+            assertNotNull(mListener);
+            assertEquals(expectedUpdateAppPackageName, mUpdateAppPackageName);
+            assertEquals(expectedDataAppPackageName, mDataAppPackageName);
+        }
+
+        public void assertNotInitialized() {
+            assertNull(mListener);
+        }
+
+        @Override
+        public void sendTriggerUpdateCheck(CheckToken checkToken) {
+            if (mLastToken != null) {
+                fail("lastToken already set");
+            }
+            mLastToken = checkToken;
+        }
+
+        @Override
+        public void enableReliabilityTriggering() {
+            mReliabilityTriggeringEnabled = true;
+        }
+
+        @Override
+        public void disableReliabilityTriggering() {
+            mReliabilityTriggeringEnabled = false;
+        }
+
+        public void assertReliabilityTriggeringEnabled() {
+            assertTrue(mReliabilityTriggeringEnabled);
+        }
+
+        public void assertReliabilityTriggeringDisabled() {
+            assertFalse(mReliabilityTriggeringEnabled);
+        }
+
+        public void assertUpdateTriggered() {
+            assertNotNull(mLastToken);
+        }
+
+        public void assertUpdateNotTriggered() {
+            assertNull(mLastToken);
+        }
+
+        public CheckToken captureAndResetLastToken() {
+            CheckToken toReturn = mLastToken;
+            assertNotNull("No update triggered", toReturn);
+            mLastToken = null;
+            return toReturn;
+        }
+
+        public void simulatePackageUpdatedEvent() {
+            mListener.triggerUpdateIfNeeded(true);
+        }
+
+        public void simulateReliabilityTrigger() {
+            mListener.triggerUpdateIfNeeded(false);
+        }
+    }
+
+    private static class FakeClockHelper implements ClockHelper {
+
+        private long currentTime = 1000;
+
+        @Override
+        public long currentTimestamp() {
+            return currentTime;
+        }
+
+        public void incrementClock(long millis) {
+            currentTime += millis;
+        }
+    }
+
+    /**
+     * Registers a mockito parameter matcher that uses {@link Intent#filterEquals(Intent)}. to
+     * check the parameter against the intent supplied.
+     */
+    private static Intent filterEquals(final Intent expected) {
+        final Matcher<Intent> m = new BaseMatcher<Intent>() {
+            @Override
+            public boolean matches(Object actual) {
+                return actual != null && expected.filterEquals((Intent) actual);
+            }
+            @Override
+            public void describeTo(Description description) {
+                description.appendText(expected.toString());
+            }
+        };
+        return argThat(m);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageVersionsTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageVersionsTest.java
new file mode 100644
index 0000000..a470f8f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageVersionsTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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 org.junit.Test;
+
+import android.support.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@SmallTest
+public class PackageVersionsTest {
+
+    @Test
+    public void equals() {
+        PackageVersions baseline =
+                new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+        assertEquals(baseline, baseline);
+
+        PackageVersions deepEqual =
+                new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+        assertEquals(baseline, deepEqual);
+
+        PackageVersions differentUpdateAppVersion =
+                new PackageVersions(2 /* updateAppVersion */, 1 /* dataAppVersion */);
+        assertFalse(baseline.equals(differentUpdateAppVersion));
+
+        PackageVersions differentDataAppVersion =
+                new PackageVersions(1 /* updateAppVersion */, 2 /* dataAppVersion */);
+        assertFalse(baseline.equals(differentDataAppVersion));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
new file mode 100644
index 0000000..a7f4c99
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -0,0 +1,924 @@
+/*
+ * 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 org.junit.Before;
+import org.junit.Test;
+
+import android.app.timezone.Callback;
+import android.app.timezone.DistroRulesVersion;
+import android.app.timezone.ICallback;
+import android.app.timezone.RulesManager;
+import android.app.timezone.RulesState;
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import javax.annotation.Nullable;
+import libcore.tzdata.shared2.DistroVersion;
+import libcore.tzdata.shared2.StagedDistroOperation;
+import libcore.tzdata.update2.TimeZoneDistroInstaller;
+
+import static com.android.server.timezone.RulesManagerService.REQUIRED_UPDATER_PERMISSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+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.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * White box interaction / unit testing of the {@link RulesManagerService}.
+ */
+public class RulesManagerServiceTest {
+
+    private RulesManagerService mRulesManagerService;
+
+    private FakeExecutor mFakeExecutor;
+    private PermissionHelper mMockPermissionHelper;
+    private FileDescriptorHelper mMockFileDescriptorHelper;
+    private PackageTracker mMockPackageTracker;
+    private TimeZoneDistroInstaller mMockTimeZoneDistroInstaller;
+
+    @Before
+    public void setUp() {
+        mFakeExecutor = new FakeExecutor();
+
+        mMockFileDescriptorHelper = mock(FileDescriptorHelper.class);
+        mMockPackageTracker = mock(PackageTracker.class);
+        mMockPermissionHelper = mock(PermissionHelper.class);
+        mMockTimeZoneDistroInstaller = mock(TimeZoneDistroInstaller.class);
+
+        mRulesManagerService = new RulesManagerService(
+                mMockPermissionHelper,
+                mFakeExecutor,
+                mMockFileDescriptorHelper,
+                mMockPackageTracker,
+                mMockTimeZoneDistroInstaller);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void getRulesState_noCallerPermission() throws Exception {
+        configureCallerDoesNotHavePermission();
+        mRulesManagerService.getRulesState();
+    }
+
+    @Test(expected = SecurityException.class)
+    public void requestInstall_noCallerPermission() throws Exception {
+        configureCallerDoesNotHavePermission();
+        mRulesManagerService.requestInstall(null, null, null);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void requestUninstall_noCallerPermission() throws Exception {
+        configureCallerDoesNotHavePermission();
+        mRulesManagerService.requestUninstall(null, null);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void requestNothing_noCallerPermission() throws Exception {
+        configureCallerDoesNotHavePermission();
+        mRulesManagerService.requestNothing(null, true);
+    }
+
+    @Test
+    public void getRulesState_systemRulesError() throws Exception {
+        configureDeviceCannotReadSystemRulesVersion();
+
+        assertNull(mRulesManagerService.getRulesState());
+    }
+
+    @Test
+    public void getRulesState_stagedInstall() throws Exception {
+        configureCallerHasPermission();
+
+        configureDeviceSystemRulesVersion("2016a");
+
+        DistroVersion stagedDistroVersion = new DistroVersion(
+                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                "2016c",
+                3);
+        configureStagedInstall(stagedDistroVersion);
+
+        DistroVersion installedDistroVersion = new DistroVersion(
+                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                "2016b",
+                4);
+        configureInstalledDistroVersion(installedDistroVersion);
+
+        DistroRulesVersion stagedDistroRulesVersion = new DistroRulesVersion(
+                stagedDistroVersion.rulesVersion, stagedDistroVersion.revision);
+        DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+                installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+        RulesState expectedRuleState = new RulesState(
+                "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+                false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_INSTALL, stagedDistroRulesVersion,
+                RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+        assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+    }
+
+    @Test
+    public void getRulesState_nothingStaged() throws Exception {
+        configureCallerHasPermission();
+
+        configureDeviceSystemRulesVersion("2016a");
+
+        configureNoStagedOperation();
+
+        DistroVersion installedDistroVersion = new DistroVersion(
+                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                "2016b",
+                4);
+        configureInstalledDistroVersion(installedDistroVersion);
+
+        DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+                installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+        RulesState expectedRuleState = new RulesState(
+                "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+                false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+        assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+    }
+
+    @Test
+    public void getRulesState_uninstallStaged() throws Exception {
+        configureCallerHasPermission();
+
+        configureDeviceSystemRulesVersion("2016a");
+
+        configureStagedUninstall();
+
+        DistroVersion installedDistroVersion = new DistroVersion(
+                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                "2016b",
+                4);
+        configureInstalledDistroVersion(installedDistroVersion);
+
+        DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+                installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+        RulesState expectedRuleState = new RulesState(
+                "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+                false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+        assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+    }
+
+    @Test
+    public void getRulesState_installedRulesError() throws Exception {
+        configureCallerHasPermission();
+
+        String systemRulesVersion = "2016a";
+        configureDeviceSystemRulesVersion(systemRulesVersion);
+
+        configureStagedUninstall();
+        configureDeviceCannotReadInstalledDistroVersion();
+
+        RulesState expectedRuleState = new RulesState(
+                "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+                false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+        assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+    }
+
+    @Test
+    public void getRulesState_stagedRulesError() throws Exception {
+        configureCallerHasPermission();
+
+        String systemRulesVersion = "2016a";
+        configureDeviceSystemRulesVersion(systemRulesVersion);
+
+        configureDeviceCannotReadStagedDistroOperation();
+
+        DistroVersion installedDistroVersion = new DistroVersion(
+                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                "2016b",
+                4);
+        configureInstalledDistroVersion(installedDistroVersion);
+
+        DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+                installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+        RulesState expectedRuleState = new RulesState(
+                "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+                false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+        assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+    }
+
+    @Test
+    public void getRulesState_noInstalledRules() throws Exception {
+        configureCallerHasPermission();
+
+        String systemRulesVersion = "2016a";
+        configureDeviceSystemRulesVersion(systemRulesVersion);
+        configureNoStagedOperation();
+        configureInstalledDistroVersion(null);
+
+        RulesState expectedRuleState = new RulesState(
+                systemRulesVersion, RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+                false /* operationInProgress */,
+                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+                RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+        assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+    }
+
+    @Test
+    public void getRulesState_operationInProgress() throws Exception {
+        configureCallerHasPermission();
+
+        String systemRulesVersion = "2016a";
+        String installedRulesVersion = "2016b";
+        int revision = 3;
+
+        configureDeviceSystemRulesVersion(systemRulesVersion);
+
+        DistroVersion installedDistroVersion = new DistroVersion(
+                DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+                DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+                installedRulesVersion,
+                revision);
+        configureInstalledDistroVersion(installedDistroVersion);
+
+        byte[] expectedContent = createArbitraryBytes(1000);
+        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+        // Start an async operation so there is one in progress. The mFakeExecutor won't actually
+        // execute it.
+        byte[] tokenBytes = createArbitraryTokenBytes();
+        ICallback callback = new StubbedCallback();
+
+        mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
+
+        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());
+    }
+
+    @Test
+    public void requestInstall_operationInProgress() throws Exception {
+        configureCallerHasPermission();
+
+        byte[] expectedContent = createArbitraryBytes(1000);
+        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+        byte[] tokenBytes = createArbitraryTokenBytes();
+        ICallback callback = new StubbedCallback();
+
+        // First request should succeed.
+        assertEquals(RulesManager.SUCCESS,
+                mRulesManagerService.requestInstall(parcelFileDescriptor, 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.
+        assertEquals(RulesManager.ERROR_OPERATION_IN_PROGRESS,
+                mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+        // Assert nothing async was enqueued.
+        mFakeExecutor.assertNothingQueued();
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+    }
+
+    @Test
+    public void requestInstall_badToken() throws Exception {
+        configureCallerHasPermission();
+
+        byte[] expectedContent = createArbitraryBytes(1000);
+        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+        byte[] badTokenBytes = new byte[2];
+        ICallback callback = new StubbedCallback();
+
+        try {
+            mRulesManagerService.requestInstall(parcelFileDescriptor, badTokenBytes, callback);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        // Assert nothing async was enqueued.
+        mFakeExecutor.assertNothingQueued();
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+    }
+
+    @Test
+    public void requestInstall_nullParcelFileDescriptor() throws Exception {
+        configureCallerHasPermission();
+
+        ParcelFileDescriptor parcelFileDescriptor = null;
+        byte[] tokenBytes = createArbitraryTokenBytes();
+        ICallback callback = new StubbedCallback();
+
+        try {
+            mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
+            fail();
+        } catch (NullPointerException expected) {}
+
+        // Assert nothing async was enqueued.
+        mFakeExecutor.assertNothingQueued();
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+    }
+
+    @Test
+    public void requestInstall_nullCallback() throws Exception {
+        configureCallerHasPermission();
+
+        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+        byte[] tokenBytes = createArbitraryTokenBytes();
+        ICallback callback = null;
+
+        try {
+            mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
+            fail();
+        } catch (NullPointerException expected) {}
+
+        // Assert nothing async was enqueued.
+        mFakeExecutor.assertNothingQueued();
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+    }
+
+    @Test
+    public void requestInstall_asyncSuccess() throws Exception {
+        configureCallerHasPermission();
+
+        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+        byte[] expectedContent = createArbitraryBytes(1000);
+        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+        CheckToken token = createArbitraryToken();
+        byte[] tokenBytes = token.toByteArray();
+
+        TestCallback callback = new TestCallback();
+
+        // Request the install.
+        assertEquals(RulesManager.SUCCESS,
+                mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+        // Assert nothing has happened yet.
+        callback.assertNoResultReceived();
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+
+        // Set up the installer.
+        configureStageInstallExpectation(expectedContent, TimeZoneDistroInstaller.INSTALL_SUCCESS);
+
+        // Simulate the async execution.
+        mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // Verify the expected calls were made to other components.
+        verifyStageInstallCalled(expectedContent);
+        verifyPackageTrackerCalled(token, true /* success */);
+
+        // Check the callback was called.
+        callback.assertResultReceived(Callback.SUCCESS);
+    }
+
+    @Test
+    public void requestInstall_nullTokenBytes() throws Exception {
+        configureCallerHasPermission();
+
+        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+        byte[] expectedContent = createArbitraryBytes(1000);
+        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+        TestCallback callback = new TestCallback();
+
+        // Request the install.
+        assertEquals(RulesManager.SUCCESS,
+                mRulesManagerService.requestInstall(
+                        parcelFileDescriptor, null /* tokenBytes */, callback));
+
+        // Assert nothing has happened yet.
+        verifyNoInstallerCallsMade();
+        callback.assertNoResultReceived();
+
+        // Set up the installer.
+        configureStageInstallExpectation(expectedContent, TimeZoneDistroInstaller.INSTALL_SUCCESS);
+
+        // Simulate the async execution.
+        mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // Verify the expected calls were made to other components.
+        verifyStageInstallCalled(expectedContent);
+        verifyPackageTrackerCalled(null /* expectedToken */, true /* success */);
+
+        // Check the callback was received.
+        callback.assertResultReceived(Callback.SUCCESS);
+    }
+
+    @Test
+    public void requestInstall_asyncInstallFail() throws Exception {
+        configureCallerHasPermission();
+
+        byte[] expectedContent = createArbitraryBytes(1000);
+        ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+        configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+        CheckToken token = createArbitraryToken();
+        byte[] tokenBytes = token.toByteArray();
+
+        TestCallback callback = new TestCallback();
+
+        // Request the install.
+        assertEquals(RulesManager.SUCCESS,
+                mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+        // Assert nothing has happened yet.
+        verifyNoInstallerCallsMade();
+        callback.assertNoResultReceived();
+
+        // Set up the installer.
+        configureStageInstallExpectation(
+                expectedContent, TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR);
+
+        // Simulate the async execution.
+        mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // Verify the expected calls were made to other components.
+        verifyStageInstallCalled(expectedContent);
+
+        // Validation failure is treated like a successful check: repeating it won't improve things.
+        boolean expectedSuccess = true;
+        verifyPackageTrackerCalled(token, expectedSuccess);
+
+        // Check the callback was received.
+        callback.assertResultReceived(Callback.ERROR_INSTALL_VALIDATION_ERROR);
+    }
+
+    @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();
+
+        byte[] tokenBytes = createArbitraryTokenBytes();
+        ICallback callback = new StubbedCallback();
+
+        // First request should succeed.
+        assertEquals(RulesManager.SUCCESS,
+                mRulesManagerService.requestUninstall(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.
+        assertEquals(RulesManager.ERROR_OPERATION_IN_PROGRESS,
+                mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+        // Assert nothing async was enqueued.
+        mFakeExecutor.assertNothingQueued();
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+    }
+
+    @Test
+    public void requestUninstall_badToken() throws Exception {
+        configureCallerHasPermission();
+
+        byte[] badTokenBytes = new byte[2];
+        ICallback callback = new StubbedCallback();
+
+        try {
+            mRulesManagerService.requestUninstall(badTokenBytes, callback);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        // Assert nothing async was enqueued.
+        mFakeExecutor.assertNothingQueued();
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+    }
+
+    @Test
+    public void requestUninstall_nullCallback() throws Exception {
+        configureCallerHasPermission();
+
+        byte[] tokenBytes = createArbitraryTokenBytes();
+        ICallback callback = null;
+
+        try {
+            mRulesManagerService.requestUninstall(tokenBytes, callback);
+            fail();
+        } catch (NullPointerException expected) {}
+
+        // Assert nothing async was enqueued.
+        mFakeExecutor.assertNothingQueued();
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+    }
+
+    @Test
+    public void requestUninstall_asyncSuccess() throws Exception {
+        configureCallerHasPermission();
+
+        CheckToken token = createArbitraryToken();
+        byte[] tokenBytes = token.toByteArray();
+
+        TestCallback callback = new TestCallback();
+
+        // Request the uninstall.
+        assertEquals(RulesManager.SUCCESS,
+                mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+        // Assert nothing has happened yet.
+        callback.assertNoResultReceived();
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+
+        // Set up the installer.
+        configureStageUninstallExpectation(true /* success */);
+
+        // Simulate the async execution.
+        mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // Verify the expected calls were made to other components.
+        verifyStageUninstallCalled();
+        verifyPackageTrackerCalled(token, true /* success */);
+
+        // Check the callback was called.
+        callback.assertResultReceived(Callback.SUCCESS);
+    }
+
+    @Test
+    public void requestUninstall_nullTokenBytes() throws Exception {
+        configureCallerHasPermission();
+
+        TestCallback callback = new TestCallback();
+
+        // Request the uninstall.
+        assertEquals(RulesManager.SUCCESS,
+                mRulesManagerService.requestUninstall(null /* tokenBytes */, callback));
+
+        // Assert nothing has happened yet.
+        verifyNoInstallerCallsMade();
+        callback.assertNoResultReceived();
+
+        // Set up the installer.
+        configureStageUninstallExpectation(true /* success */);
+
+        // Simulate the async execution.
+        mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // Verify the expected calls were made to other components.
+        verifyStageUninstallCalled();
+        verifyPackageTrackerCalled(null /* expectedToken */, true /* success */);
+
+        // Check the callback was received.
+        callback.assertResultReceived(Callback.SUCCESS);
+    }
+
+    @Test
+    public void requestUninstall_asyncUninstallFail() throws Exception {
+        configureCallerHasPermission();
+
+        CheckToken token = createArbitraryToken();
+        byte[] tokenBytes = token.toByteArray();
+
+        TestCallback callback = new TestCallback();
+
+        // Request the uninstall.
+        assertEquals(RulesManager.SUCCESS,
+                mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+        // Assert nothing has happened yet.
+        verifyNoInstallerCallsMade();
+        callback.assertNoResultReceived();
+
+        // Set up the installer.
+        configureStageUninstallExpectation(false /* success */);
+
+        // Simulate the async execution.
+        mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+        // Verify the expected calls were made to other components.
+        verifyStageUninstallCalled();
+        verifyPackageTrackerCalled(token, false /* success */);
+
+        // Check the callback was received.
+        callback.assertResultReceived(Callback.ERROR_UNKNOWN_FAILURE);
+    }
+
+    @Test
+    public void requestNothing_operationInProgressOk() throws Exception {
+        configureCallerHasPermission();
+
+        // Set up a parallel operation.
+        assertEquals(RulesManager.SUCCESS,
+                mRulesManagerService.requestUninstall(null, new StubbedCallback()));
+        // Something async should be enqueued. Clear it but do not execute it to simulate it still
+        // being in progress.
+        mFakeExecutor.getAndResetLastCommand();
+
+        CheckToken token = createArbitraryToken();
+        byte[] tokenBytes = token.toByteArray();
+
+        // Make the call.
+        mRulesManagerService.requestNothing(tokenBytes, true /* success */);
+
+        // Assert nothing async was enqueued.
+        mFakeExecutor.assertNothingQueued();
+
+        // Verify the expected calls were made to other components.
+        verifyPackageTrackerCalled(token, true /* success */);
+        verifyNoInstallerCallsMade();
+    }
+
+    @Test
+    public void requestNothing_badToken() throws Exception {
+        configureCallerHasPermission();
+
+        byte[] badTokenBytes = new byte[2];
+
+        try {
+            mRulesManagerService.requestNothing(badTokenBytes, true /* success */);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        // Assert nothing async was enqueued.
+        mFakeExecutor.assertNothingQueued();
+
+        // Assert no other calls were made.
+        verifyNoInstallerCallsMade();
+        verifyNoPackageTrackerCallsMade();
+    }
+
+    @Test
+    public void requestNothing() throws Exception {
+        configureCallerHasPermission();
+
+        CheckToken token = createArbitraryToken();
+        byte[] tokenBytes = token.toByteArray();
+
+        // Make the call.
+        mRulesManagerService.requestNothing(tokenBytes, false /* success */);
+
+        // Assert everything required was done.
+        verifyNoInstallerCallsMade();
+        verifyPackageTrackerCalled(token, false /* success */);
+    }
+
+    @Test
+    public void requestNothing_nullTokenBytes() throws Exception {
+        configureCallerHasPermission();
+
+        // Make the call.
+        mRulesManagerService.requestNothing(null /* tokenBytes */, true /* success */);
+
+        // Assert everything required was done.
+        verifyNoInstallerCallsMade();
+        verifyPackageTrackerCalled(null /* token */, true /* success */);
+    }
+
+    private void verifyNoPackageTrackerCallsMade() {
+        verifyNoMoreInteractions(mMockPackageTracker);
+        reset(mMockPackageTracker);
+    }
+
+    private void verifyPackageTrackerCalled(
+            CheckToken expectedCheckToken, boolean expectedSuccess) {
+        verify(mMockPackageTracker).recordCheckResult(expectedCheckToken, expectedSuccess);
+        reset(mMockPackageTracker);
+    }
+
+    private void configureCallerHasPermission() throws Exception {
+        doNothing()
+                .when(mMockPermissionHelper)
+                .enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+    }
+
+    private void configureCallerDoesNotHavePermission() {
+        doThrow(new SecurityException("Simulated permission failure"))
+                .when(mMockPermissionHelper)
+                .enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+    }
+
+    private void configureParcelFileDescriptorReadSuccess(ParcelFileDescriptor parcelFileDescriptor,
+            byte[] content) throws Exception {
+        when(mMockFileDescriptorHelper.readFully(parcelFileDescriptor)).thenReturn(content);
+    }
+
+    private void configureParcelFileDescriptorReadFailure(ParcelFileDescriptor parcelFileDescriptor)
+            throws Exception {
+        when(mMockFileDescriptorHelper.readFully(parcelFileDescriptor))
+                .thenThrow(new IOException("Simulated failure"));
+    }
+
+    private void configureStageInstallExpectation(byte[] expectedContent, int resultCode)
+            throws Exception {
+        when(mMockTimeZoneDistroInstaller.stageInstallWithErrorCode(eq(expectedContent)))
+                .thenReturn(resultCode);
+    }
+
+    private void configureStageUninstallExpectation(boolean success) throws Exception {
+        doReturn(success).when(mMockTimeZoneDistroInstaller).stageUninstall();
+    }
+
+    private void verifyStageInstallCalled(byte[] expectedContent) throws Exception {
+        verify(mMockTimeZoneDistroInstaller).stageInstallWithErrorCode(eq(expectedContent));
+        verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
+        reset(mMockTimeZoneDistroInstaller);
+    }
+
+    private void verifyStageUninstallCalled() throws Exception {
+        verify(mMockTimeZoneDistroInstaller).stageUninstall();
+        verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
+        reset(mMockTimeZoneDistroInstaller);
+    }
+
+    private void verifyNoInstallerCallsMade() {
+        verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
+        reset(mMockTimeZoneDistroInstaller);
+    }
+
+    private static byte[] createArbitraryBytes(int length) {
+        byte[] bytes = new byte[length];
+        for (int i = 0; i < length; i++) {
+            bytes[i] = (byte) i;
+        }
+        return bytes;
+    }
+
+    private byte[] createArbitraryTokenBytes() {
+        return createArbitraryToken().toByteArray();
+    }
+
+    private CheckToken createArbitraryToken() {
+        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);
+    }
+
+    private void configureInstalledDistroVersion(@Nullable DistroVersion installedDistroVersion)
+            throws Exception {
+        when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion())
+                .thenReturn(installedDistroVersion);
+    }
+
+    private void configureStagedInstall(DistroVersion stagedDistroVersion) throws Exception {
+        when(mMockTimeZoneDistroInstaller.getStagedDistroOperation())
+                .thenReturn(StagedDistroOperation.install(stagedDistroVersion));
+    }
+
+    private void configureStagedUninstall() throws Exception {
+        when(mMockTimeZoneDistroInstaller.getStagedDistroOperation())
+                .thenReturn(StagedDistroOperation.uninstall());
+    }
+
+    private void configureNoStagedOperation() throws Exception {
+        when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()).thenReturn(null);
+    }
+
+    private void configureDeviceCannotReadStagedDistroOperation() throws Exception {
+        when(mMockTimeZoneDistroInstaller.getStagedDistroOperation())
+                .thenThrow(new IOException("Simulated failure"));
+    }
+
+    private void configureDeviceCannotReadSystemRulesVersion() throws Exception {
+        when(mMockTimeZoneDistroInstaller.getSystemRulesVersion())
+                .thenThrow(new IOException("Simulated failure"));
+    }
+
+    private void configureDeviceCannotReadInstalledDistroVersion() throws Exception {
+        when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion())
+                .thenThrow(new IOException("Simulated failure"));
+    }
+
+    private static class FakeExecutor implements Executor {
+
+        private Runnable mLastCommand;
+
+        @Override
+        public void execute(Runnable command) {
+            assertNull(mLastCommand);
+            assertNotNull(command);
+            mLastCommand = command;
+        }
+
+        public Runnable getAndResetLastCommand() {
+            assertNotNull(mLastCommand);
+            Runnable toReturn = mLastCommand;
+            mLastCommand = null;
+            return toReturn;
+        }
+
+        public void simulateAsyncExecutionOfLastCommand() {
+            Runnable toRun = getAndResetLastCommand();
+            toRun.run();
+        }
+
+        public void assertNothingQueued() {
+            assertNull(mLastCommand);
+        }
+    }
+
+    private static class TestCallback extends ICallback.Stub {
+
+        private boolean mOnFinishedCalled;
+        private int mLastError;
+
+        @Override
+        public void onFinished(int error) {
+            assertFalse(mOnFinishedCalled);
+            mOnFinishedCalled = true;
+            mLastError = error;
+        }
+
+        public void assertResultReceived(int expectedResult) {
+            assertTrue(mOnFinishedCalled);
+            assertEquals(expectedResult, mLastError);
+        }
+
+        public void assertNoResultReceived() {
+            assertFalse(mOnFinishedCalled);
+        }
+    }
+
+    private static class StubbedCallback extends ICallback.Stub {
+        @Override
+        public void onFinished(int error) {
+            fail("Unexpected call");
+        }
+    }
+}
diff --git a/services/tests/shortcutmanagerutils/Android.mk b/services/tests/shortcutmanagerutils/Android.mk
index 2818457..880bff8c 100644
--- a/services/tests/shortcutmanagerutils/Android.mk
+++ b/services/tests/shortcutmanagerutils/Android.mk
@@ -20,7 +20,8 @@
     $(call all-java-files-under, src)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    mockito-target
+    mockito-target \
+    legacy-android-test
 
 LOCAL_MODULE_TAGS := optional
 
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index b9d22e8..24cd3c7 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -682,7 +682,7 @@
             }
 
             // send broadcast intent only if the USB state has changed
-            if (!isUsbStateChanged(intent)) {
+            if (!isUsbStateChanged(intent) && !configChanged) {
                 if (DEBUG) {
                     Slog.d(TAG, "skip broadcasting " + intent + " extras: " + intent.getExtras());
                 }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 9822936..239cdcf 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -878,6 +878,7 @@
      * {@link Call#sendRttRequest()}
      */
     public static final class RttModifyStatus {
+        private RttModifyStatus() {}
         /**
          * Session modify request was successful.
          */
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index c42a835..5530cb7 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -43,6 +43,15 @@
 
     /**
      * {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
+     * sort order for {@link PhoneAccount}s from the same
+     * {@link android.telecom.ConnectionService}.
+     * @hide
+     */
+    public static final String EXTRA_SORT_ORDER =
+            "android.telecom.extra.SORT_ORDER";
+
+    /**
+     * {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
      * maximum permitted length of a call subject specified via the
      * {@link TelecomManager#EXTRA_CALL_SUBJECT} extra on an
      * {@link android.content.Intent#ACTION_CALL} intent.  Ultimately a {@link ConnectionService} is
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 5093a61..bcd01a7 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -112,20 +112,26 @@
             "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
 
     /**
-     * The {@link android.content.Intent} action used indicate that a new phone account was
-     * just registered.
-     * @hide
+     * {@link android.content.Intent} action used indicate that a new phone account was just
+     * registered.
+     * <p>
+     * The Intent {@link Intent#getExtras() extras} will contain {@link #EXTRA_PHONE_ACCOUNT_HANDLE}
+     * to indicate which {@link PhoneAccount} was registered.
+     * <p>
+     * Will only be sent to the default dialer app (see {@link #getDefaultDialerPackage()}).
      */
-    @SystemApi
     public static final String ACTION_PHONE_ACCOUNT_REGISTERED =
             "android.telecom.action.PHONE_ACCOUNT_REGISTERED";
 
     /**
-     * The {@link android.content.Intent} action used indicate that a phone account was
-     * just unregistered.
-     * @hide
+     * {@link android.content.Intent} action used indicate that a phone account was just
+     * unregistered.
+     * <p>
+     * The Intent {@link Intent#getExtras() extras} will contain {@link #EXTRA_PHONE_ACCOUNT_HANDLE}
+     * to indicate which {@link PhoneAccount} was unregistered.
+     * <p>
+     * Will only be sent to the default dialer app (see {@link #getDefaultDialerPackage()}).
      */
-    @SystemApi
     public static final String ACTION_PHONE_ACCOUNT_UNREGISTERED =
             "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED";
 
@@ -755,6 +761,32 @@
     }
 
     /**
+     * Returns a list of {@link PhoneAccountHandle}s for self-managed {@link ConnectionService}s.
+     * <p>
+     * Self-Managed {@link ConnectionService}s have a {@link PhoneAccount} with
+     * {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.
+     * <p>
+     * Requires permission {@link android.Manifest.permission#READ_PHONE_STATE}, or that the caller
+     * is the default dialer app.
+     * <p>
+     * A {@link SecurityException} will be thrown if a called is not the default dialer, or lacks
+     * the {@link android.Manifest.permission#READ_PHONE_STATE} permission.
+     *
+     * @return A list of {@code PhoneAccountHandle} objects.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getSelfManagedPhoneAccounts()", e);
+        }
+        return new ArrayList<>();
+    }
+
+    /**
      * Returns a list of {@link PhoneAccountHandle}s including those which have not been enabled
      * by the user.
      *
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index 429a434..bae58ff 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -25,6 +25,7 @@
 import android.telecom.InCallService.VideoCall;
 import android.view.Surface;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
@@ -44,7 +45,8 @@
     private int mVideoQuality = VideoProfile.QUALITY_UNKNOWN;
     private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
     private final String mCallingPackageName;
-    private final int mTargetSdkVersion;
+
+    private int mTargetSdkVersion;
 
     private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
         @Override
@@ -207,7 +209,12 @@
         mBinder = new VideoCallListenerBinder();
         mVideoProvider.addVideoCallback(mBinder);
         mCallingPackageName = callingPackageName;
-        mTargetSdkVersion = targetSdkVersion;
+        setTargetSdkVersion(targetSdkVersion);
+    }
+
+    @VisibleForTesting
+    public void setTargetSdkVersion(int sdkVersion) {
+        mTargetSdkVersion = sdkVersion;
     }
 
     public void destroy() {
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index c044742..86f7d7d 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -59,6 +59,11 @@
             boolean includeDisabledAccounts, String callingPackage);
 
     /**
+     * @see TelecomServiceImpl#getSelfManagedPhoneAccounts
+     */
+    List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage);
+
+    /**
      * @see TelecomManager#getPhoneAccountsSupportingScheme
      */
     List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(in String uriScheme,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7e0fbb9..e3d66e7 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -57,6 +57,13 @@
     // system image, that can be added in packages/apps/CarrierConfig.
 
     /**
+     * This flag specifies whether VoLTE availability is based on provisioning. By default this is
+     * false.
+     */
+    public static final String
+            KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
+
+    /**
      * Flag indicating whether the Phone app should ignore EVENT_SIM_NETWORK_LOCKED
      * events from the Sim.
      * If true, this will prevent the IccNetworkDepersonalizationPanel from being shown, and
@@ -567,11 +574,19 @@
     public static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS =
             "carrier_metered_apn_types_strings";
     /**
-     * Default APN types that are roamig-metered by the carrier
+     * Default APN types that are roaming-metered by the carrier
      * @hide
      */
     public static final String KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS =
             "carrier_metered_roaming_apn_types_strings";
+
+    /**
+     * Default APN types that are metered on IWLAN by the carrier
+     * @hide
+     */
+    public static final String KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS =
+            "carrier_metered_iwlan_apn_types_strings";
+
     /**
      * CDMA carrier ERI (Enhanced Roaming Indicator) file name
      * @hide
@@ -604,6 +619,38 @@
         "vvm_cellular_data_required_bool";
 
     /**
+     * The default OMTP visual voicemail client prefix to use. Defaulted to "//VVM"
+     */
+    public static final String KEY_VVM_CLIENT_PREFIX_STRING =
+            "vvm_client_prefix_string";
+
+    /**
+     * Whether to use SSL to connect to the visual voicemail IMAP server. Defaulted to false.
+     */
+    public static final String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
+
+    /**
+     * A set of capabilities that should not be used even if it is reported by the visual voicemail
+     * IMAP CAPABILITY command.
+     */
+    public static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY =
+            "vvm_disabled_capabilities_string_array";
+
+    /**
+     * Whether legacy mode should be used when the visual voicemail client is disabled.
+     *
+     * <p>Legacy mode is a mode that on the carrier side visual voicemail is still activated, but on
+     * the client side all network operations are disabled. SMSs are still monitored so a new
+     * message SYNC SMS will be translated to show a message waiting indicator, like traditional
+     * voicemails.
+     *
+     * <p>This is for carriers that does not support VVM deactivation so voicemail can continue to
+     * function without the data cost.
+     */
+    public static final String KEY_VVM_LEGACY_MODE_ENABLED_BOOL =
+            "vvm_legacy_mode_enabled_bool";
+
+    /**
      * Whether to prefetch audio data on new voicemail arrival, defaulted to true.
      */
     public static final String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
@@ -611,10 +658,20 @@
     /**
      * The package name of the carrier's visual voicemail app to ensure that dialer visual voicemail
      * and carrier visual voicemail are not active at the same time.
+     *
+     * @deprecated use {@link #KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY}.
      */
+    @Deprecated
     public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
 
     /**
+     * A list of the carrier's visual voicemail app package names to ensure that dialer visual
+     * voicemail and carrier visual voicemail are not active at the same time.
+     */
+    public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY =
+            "carrier_vvm_package_name_string_array";
+
+    /**
      * Flag specifying whether ICCID is showed in SIM Status screen, default to false.
      */
     public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
@@ -838,6 +895,12 @@
     public static final String KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL =
             "stk_disable_launch_browser_bool";
 
+    /**
+     * Boolean indicating if show data RAT icon on status bar even when data is disabled
+     * @hide
+     */
+    public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL =
+            "always_show_data_rat_icon_bool";
 
     // These variables are used by the MMS service and exposed through another API, {@link
     // SmsManager}. The variable names and string values are copied from there.
@@ -936,6 +999,20 @@
             "carrier_default_actions_on_dcfailure_string_array";
 
     /**
+     * Defines carrier-specific actions which act upon
+     * com.android.internal.telephony.CARRIER_SIGNAL_RESET, used for customization of the
+     * default carrier app
+     * Format: "CARRIER_ACTION_IDX, ..."
+     * Where {@code CARRIER_ACTION_IDX} is an integer defined in
+     * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+     * Example:
+     * {@link com.android.carrierdefaultapp.CarrierActionUtils
+     * #CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS clear all notifications on reset}
+     * @hide
+     */
+    public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET =
+            "carrier_default_actions_on_reset_string_array";
+    /**
      * Defines a list of acceptable redirection url for default carrier app
      * @hides
      */
@@ -1314,6 +1391,8 @@
         sDefaults.putBoolean(KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false);
         sDefaults.putBoolean(KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL, false);
         sDefaults.putBoolean(KEY_HIDE_SIM_LOCK_SETTINGS_BOOL, false);
+
+        sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONED_BOOL, false);
         sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false);
         sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false);
         sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
@@ -1338,8 +1417,13 @@
         sDefaults.putInt(KEY_VVM_PORT_NUMBER_INT, 0);
         sDefaults.putString(KEY_VVM_TYPE_STRING, "");
         sDefaults.putBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL, false);
+        sDefaults.putString(KEY_VVM_CLIENT_PREFIX_STRING,"//VVM");
+        sDefaults.putBoolean(KEY_VVM_SSL_ENABLED_BOOL,false);
+        sDefaults.putStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY, null);
+        sDefaults.putBoolean(KEY_VVM_LEGACY_MODE_ENABLED_BOOL,false);
         sDefaults.putBoolean(KEY_VVM_PREFETCH_BOOL, true);
         sDefaults.putString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING, "");
+        sDefaults.putStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL, false);
         sDefaults.putBoolean(KEY_CI_ACTION_ON_SYS_UPDATE_BOOL, false);
         sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING, "");
@@ -1367,6 +1451,9 @@
                 new String[]{"default", "mms", "dun", "supl"});
         sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{"default", "mms", "dun", "supl"});
+        // By default all APNs are unmetered if the device is on IWLAN.
+        sDefaults.putStringArray(KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS,
+                new String[]{});
 
         sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
                 new int[]{
@@ -1451,7 +1538,8 @@
         sDefaults.putStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
                 new String[]{
                         "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:" +
-                                "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED"
+                                "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED," +
+                                "com.android.internal.telephony.CARRIER_SIGNAL_RESET"
                 });
         sDefaults.putStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null);
 
@@ -1462,6 +1550,9 @@
                         //4: CARRIER_ACTION_DISABLE_METERED_APNS
                         //1: CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
                 });
+        sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET, new String[]{
+                "6" //6: CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
+                });
         sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null);
 
         // Rat families: {GPRS, EDGE}, {EVDO, EVDO_A, EVDO_B}, {UMTS, HSPA, HSDPA, HSUPA, HSPAP},
@@ -1491,6 +1582,7 @@
         sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
         sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
+        sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
     }
 
     /**
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
new file mode 100644
index 0000000..d9f8fa6
--- /dev/null
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -0,0 +1,325 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.mbms.DownloadCallback;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStatus;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
+import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.vendor.IMbmsDownloadService;
+import android.util.Log;
+
+import java.util.List;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+/** @hide */
+public class MbmsDownloadManager {
+    private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
+
+    /**
+     * The MBMS middleware should send this when a download of single file has completed or
+     * failed. Mandatory extras are
+     * {@link #EXTRA_RESULT}
+     * {@link #EXTRA_INFO}
+     * {@link #EXTRA_REQUEST}
+     * {@link #EXTRA_TEMP_LIST}
+     * {@link #EXTRA_FINAL_URI}
+     *
+     * TODO: future systemapi
+     */
+    public static final String ACTION_DOWNLOAD_RESULT_INTERNAL =
+            "android.telephony.mbms.action.DOWNLOAD_RESULT_INTERNAL";
+
+    /**
+     * The MBMS middleware should send this when it wishes to request {@code content://} URIs to
+     * serve as temp files for downloads or when it wishes to resume paused downloads. Mandatory
+     * extras are
+     * {@link #EXTRA_REQUEST}
+     *
+     * Optional extras are
+     * {@link #EXTRA_FD_COUNT} (0 if not present)
+     * {@link #EXTRA_PAUSED_LIST} (empty if not present)
+     *
+     * TODO: future systemapi
+     */
+    public static final String ACTION_FILE_DESCRIPTOR_REQUEST =
+            "android.telephony.mbms.action.FILE_DESCRIPTOR_REQUEST";
+
+    /**
+     * The MBMS middleware should send this when it wishes to clean up temp  files in the app's
+     * filesystem. Mandatory extras are:
+     * {@link #EXTRA_TEMP_FILES_IN_USE}
+     *
+     * TODO: future systemapi
+     */
+    public static final String ACTION_CLEANUP =
+            "android.telephony.mbms.action.CLEANUP";
+
+    /**
+     * Integer extra indicating the result code of the download.
+     * TODO: put in link to error list
+     * TODO: future systemapi (here and and all extras)
+     */
+    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.
+     */
+    public static final String EXTRA_INFO = "android.telephony.mbms.extra.INFO";
+
+    /**
+     * Extra containing the {@link DownloadRequest} for which the download result or file
+     * descriptor request is for. Must not be null.
+     */
+    public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
+
+    /**
+     * Extra containing a {@link List} of {@link Uri}s that were used as temp files for this
+     * completed file. These {@link Uri}s should have scheme {@code file://}, and the temp
+     * files will be deleted upon receipt of the intent.
+     * May be null.
+     */
+    public static final String EXTRA_TEMP_LIST = "android.telephony.mbms.extra.TEMP_LIST";
+
+    /**
+     * Extra containing a single {@link Uri} indicating the path to the temp file in which the
+     * decoded downloaded file resides. Must not be null.
+     */
+    public static final String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI";
+
+    /**
+     * Extra containing an integer indicating the number of temp files requested.
+     */
+    public static final String EXTRA_FD_COUNT = "android.telephony.mbms.extra.FD_COUNT";
+
+    /**
+     * Extra containing a list of {@link Uri}s that the middleware is requesting access to via
+     * {@link #ACTION_FILE_DESCRIPTOR_REQUEST} in order to resume downloading. These {@link Uri}s
+     * should have scheme {@code file://}.
+     */
+    public static final String EXTRA_PAUSED_LIST = "android.telephony.mbms.extra.PAUSED_LIST";
+
+    /**
+     * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
+     * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These are temp files that are meant
+     * to be used for new file downloads.
+     */
+    public static final String EXTRA_FREE_URI_LIST = "android.telephony.mbms.extra.FREE_URI_LIST";
+
+    /**
+     * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
+     * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These
+     * {@link android.telephony.mbms.UriPathPair}s contain {@code content://} URIs that provide
+     * access to previously paused downloads.
+     */
+    public static final String EXTRA_PAUSED_URI_LIST =
+            "android.telephony.mbms.extra.PAUSED_URI_LIST";
+
+    /**
+     * Extra containing a list of {@link Uri}s indicating temp files which the middleware is
+     * still using.
+     */
+    public static final String EXTRA_TEMP_FILES_IN_USE =
+            "android.telephony.mbms.extra.TEMP_FILES_IN_USE";
+
+    public static final int RESULT_SUCCESSFUL = 1;
+    public static final int RESULT_CANCELLED  = 2;
+    public static final int RESULT_EXPIRED    = 3;
+    // TODO - more results!
+
+    private final Context mContext;
+    private int mSubId = INVALID_SUBSCRIPTION_ID;
+
+    private IMbmsDownloadService mService;
+    private final IMbmsDownloadManagerCallback mCallback;
+    private final String mDownloadAppName;
+
+    private MbmsDownloadManager(Context context, IMbmsDownloadManagerCallback callback,
+            String downloadAppName, int subId) {
+        mContext = context;
+        mCallback = callback;
+        mDownloadAppName = downloadAppName;
+        mSubId = subId;
+    }
+
+    /**
+     * Create a new MbmsDownloadManager using the system default data subscription ID.
+     *
+     * Note that this call will bind a remote service and that may take a bit.  This
+     * may throw an Illegal ArgumentException or RemoteException.
+     *
+     * @hide
+     */
+    public static MbmsDownloadManager createManager(Context context,
+            IMbmsDownloadManagerCallback listener, String downloadAppName)
+            throws MbmsException {
+        MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
+                SubscriptionManager.getDefaultSubscriptionId());
+        mdm.bindAndInitialize();
+        return mdm;
+    }
+
+    /**
+     * Create a new MbmsDownloadManager using the given subscription ID.
+     *
+     * Note that this call will bind a remote service and that may take a bit.  This
+     * may throw an Illegal ArgumentException or RemoteException.
+     *
+     * @hide
+     */
+
+    public static MbmsDownloadManager createManager(Context context,
+            IMbmsDownloadManagerCallback listener, String downloadAppName, int subId)
+            throws MbmsException {
+        MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
+                subId);
+        mdm.bindAndInitialize();
+        return mdm;
+    }
+
+    private void bindAndInitialize() throws MbmsException {
+        // TODO: bind
+        try {
+            mService.initialize(mDownloadAppName, mSubId, mCallback);
+        } catch (RemoteException e) {
+            throw new MbmsException(0); // TODO: proper error code
+        }
+    }
+
+    /**
+     * Gets the list of files published for download.
+     * They may occur at times far in the future.
+     * servicesClasses lets the app filter on types of files and is opaque data between
+     *     the app and the carrier
+     *
+     * Multiple calls replace trhe list of serviceClasses of interest.
+     *
+     * May throw an IllegalArgumentException or RemoteException.
+     *
+     * Synchronous responses include
+     * <li>SUCCESS</li>
+     * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
+     *
+     * Asynchronous errors through the listener include any of the errors except
+     * <li>ERROR_MSDC_UNABLE_TO_)START_SERVICE</li>
+     * <li>ERROR_MSDC_INVALID_SERVICE_ID</li>
+     * <li>ERROR_MSDC_END_OF_SESSION</li>
+     */
+    public int getFileServices(List<String> serviceClasses) {
+        return 0;
+    }
+
+
+    /**
+     * Requests a future download.
+     * returns a token which may be used to cancel a download.
+     * downloadListener is an optional callback object which can be used to get progress reports
+     *     of a currently occuring download.  Note this can only run while the calling app
+     *     is running, so future downloads will simply result in resultIntents being sent
+     *     for completed or errored-out downloads.  A NULL indicates no callbacks are needed.
+     *
+     * May throw an IllegalArgumentException or RemoteExcpetion.
+     *
+     * Asynchronous errors through the listener include any of the errors
+     */
+    public DownloadRequest download(DownloadRequest downloadRequest, DownloadCallback listener) {
+        return null;
+    }
+
+    /**
+     * 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>
+     */
+    public List<DownloadRequest> listPendingDownloads() {
+        return null;
+    }
+
+    /**
+     * Attempts to cancel the specified DownloadRequest.
+     *
+     * May throw a RemoteException.
+     *
+     * Synchronous responses may include
+     * <li>SUCCESS</li>
+     * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
+     * <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
+     */
+    public int cancelDownload(DownloadRequest downloadRequest) {
+        return 0;
+    }
+
+    /**
+     * Gets information about current and known upcoming downloads.
+     *
+     * 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.
+     *
+     * May throw an IllegalArgumentException or RemoteException.
+     *
+     * If the DownloadRequest is unknown the results will be null.
+     */
+    public DownloadStatus getDownloadStatus(DownloadRequest downloadRequest) {
+        return null;
+    }
+
+    /**
+     * Resets middleware knowledge regarding 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.
+     *
+     * May throw an IllegalArgumentException or RemoteException.
+     *
+     * <li>SUCCESS</li>
+     * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
+     * <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
+     */
+    public int resetDownloadKnowledge(DownloadRequest downloadRequest) {
+        return 0;
+    }
+
+    public void dispose() {
+        try {
+            if (mService != null) {
+                mService.dispose(mDownloadAppName, mSubId);
+            } else {
+                Log.i(LOG_TAG, "Service already dead");
+            }
+        } catch (RemoteException e) {
+            // Ignore
+            Log.i(LOG_TAG, "Remote exception while disposing of service");
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java
new file mode 100644
index 0000000..6754426
--- /dev/null
+++ b/telephony/java/android/telephony/MbmsStreamingManager.java
@@ -0,0 +1,305 @@
+/*
+ * 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;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.RemoteException;
+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 android.telephony.mbms.vendor.IMbmsStreamingService;
+import android.util.Log;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+/** @hide */
+public class MbmsStreamingManager {
+    private interface ServiceListener {
+        void onServiceConnected();
+        void onServiceDisconnected();
+    }
+
+    private static final String LOG_TAG = "MbmsStreamingManager";
+    public static final String MBMS_STREAMING_SERVICE_ACTION =
+            "android.telephony.action.EmbmsStreaming";
+
+    private static final boolean DEBUG = true;
+    private static final int BIND_TIMEOUT_MS = 3000;
+
+    private IMbmsStreamingService mService;
+    private ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (service != null) {
+                Log.i(LOG_TAG, String.format("Connected to service %s", name));
+                synchronized (MbmsStreamingManager.this) {
+                    mService = IMbmsStreamingService.Stub.asInterface(service);
+                    mServiceListeners.forEach(ServiceListener::onServiceConnected);
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.i(LOG_TAG, String.format("Disconnected from service %s", name));
+            synchronized (MbmsStreamingManager.this) {
+                mService = null;
+                mServiceListeners.forEach(ServiceListener::onServiceDisconnected);
+            }
+        }
+    };
+    private List<ServiceListener> mServiceListeners = new LinkedList<>();
+
+    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) {
+        mContext = context;
+        mAppName = streamingAppName;
+        mCallbackToApp = listener;
+        mSubscriptionId = subscriptionId;
+    }
+
+    /**
+     * Create a new MbmsStreamingManager using the given subscription ID.
+     *
+     * Note that this call will bind a remote service. You may not call this method on your app's
+     * main thread. This may throw an {@link MbmsException}, indicating errors that may happen
+     * during the initialization or binding process.
+     *
+     * @param context The {@link Context} to use.
+     * @param listener 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)
+            throws MbmsException {
+        MbmsStreamingManager manager = new MbmsStreamingManager(context, listener,
+                streamingAppName, subscriptionId);
+        manager.bindAndInitialize();
+        return manager;
+    }
+
+    /**
+     * Create a new MbmsStreamingManager using the system default data subscription ID.
+     * See {@link #create(Context, MbmsStreamingManagerCallback, String, int)}.
+     */
+    public static MbmsStreamingManager create(Context context,
+            MbmsStreamingManagerCallback listener, String streamingAppName)
+            throws MbmsException {
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
+        MbmsStreamingManager manager = new MbmsStreamingManager(context, listener,
+                streamingAppName, subId);
+        manager.bindAndInitialize();
+        return manager;
+    }
+
+    /**
+     * Terminates this instance, ending calls to the registered listener.  Also terminates
+     * any streaming services spawned from this instance.
+     */
+    public synchronized void dispose() {
+        if (mService == null) {
+            // Ignore and return, assume already disposed.
+            return;
+        }
+        try {
+            mService.dispose(mAppName, mSubscriptionId);
+        } catch (RemoteException e) {
+            // Ignore for now
+        }
+        mService = null;
+    }
+
+    /**
+     * An inspection API to retrieve the list of streaming media currently be advertised.
+     * The results are returned asynchronously through the previously registered callback.
+     * serviceClasses lets the app filter on types of programming and is opaque data between
+     * the app and the carrier.
+     *
+     * 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_UNKNOWN_REMOTE_EXCEPTION}
+     * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
+     * {@link MbmsException#ERROR_SERVICE_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}
+     */
+    public void getStreamingServices(List<String> classList) throws MbmsException {
+        if (mService == null) {
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+        }
+        try {
+            int returnCode = mService.getStreamingServices(mAppName, mSubscriptionId, classList);
+            if (returnCode != MbmsException.SUCCESS) {
+                throw new MbmsException(returnCode);
+            }
+        } catch (DeadObjectException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService = null;
+            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+        } catch (RemoteException e) {
+            throw new MbmsException(MbmsException.ERROR_UNKNOWN_REMOTE_EXCEPTION);
+        }
+    }
+
+    /**
+     * 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#streamStateChanged(int)}.
+     *
+     * May throw an {@link MbmsException} containing any of the following error codes:
+     * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
+     * {@link MbmsException#ERROR_UNKNOWN_REMOTE_EXCEPTION}
+     * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
+     * {@link MbmsException#ERROR_SERVICE_LOST}
+     *
+     * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+     *
+     * Asynchronous errors through the listener include any of the errors
+     */
+    public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
+            StreamingServiceCallback listener) throws MbmsException {
+        if (mService == null) {
+            throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+        }
+
+        try {
+            int returnCode = mService.startStreaming(
+                    mAppName, mSubscriptionId, serviceInfo.getServiceId(), listener);
+            if (returnCode != MbmsException.SUCCESS) {
+                throw new MbmsException(returnCode);
+            }
+        } catch (DeadObjectException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService = null;
+            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+        } catch (RemoteException e) {
+            throw new MbmsException(MbmsException.ERROR_UNKNOWN_REMOTE_EXCEPTION);
+        }
+
+        return new StreamingService(
+                mAppName, mSubscriptionId, mService, serviceInfo, listener);
+    }
+
+    private void bindAndInitialize() throws MbmsException {
+        // Query for the proper service
+        PackageManager packageManager = mContext.getPackageManager();
+        Intent queryIntent = new Intent();
+        queryIntent.setAction(MBMS_STREAMING_SERVICE_ACTION);
+        List<ResolveInfo> streamingServices = packageManager.queryIntentServices(queryIntent,
+                PackageManager.MATCH_SYSTEM_ONLY);
+
+        if (streamingServices == null || streamingServices.size() == 0) {
+            throw new MbmsException(
+                    MbmsException.ERROR_NO_SERVICE_INSTALLED);
+        }
+        if (streamingServices.size() > 1) {
+            throw new MbmsException(
+                    MbmsException.ERROR_MULTIPLE_SERVICES_INSTALLED);
+        }
+
+        // Kick off the binding, and synchronously wait until binding is complete
+        final CountDownLatch latch = new CountDownLatch(1);
+        ServiceListener bindListener = new ServiceListener() {
+            @Override
+            public void onServiceConnected() {
+                latch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected() {
+            }
+        };
+
+        synchronized (this) {
+            mServiceListeners.add(bindListener);
+        }
+
+        Intent bindIntent = new Intent();
+        bindIntent.setComponent(streamingServices.get(0).getComponentInfo().getComponentName());
+
+        mContext.bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+
+        waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
+
+        // Remove the listener and call the initialization method through the interface.
+        synchronized (this) {
+            mServiceListeners.remove(bindListener);
+
+            if (mService == null) {
+                throw new MbmsException(MbmsException.ERROR_BIND_TIMEOUT_OR_FAILURE);
+            }
+
+            try {
+                int returnCode = mService.initialize(mCallbackToApp, mAppName, mSubscriptionId);
+                if (returnCode != MbmsException.SUCCESS) {
+                    throw new MbmsException(returnCode);
+                }
+            } catch (RemoteException e) {
+                mService = null;
+                Log.e(LOG_TAG, "Service died before initialization");
+                if (e instanceof DeadObjectException) {
+                    throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+                } else {
+                    throw new MbmsException(MbmsException.ERROR_UNKNOWN_REMOTE_EXCEPTION);
+                }
+            }
+        }
+    }
+
+    private static void waitOnLatchWithTimeout(CountDownLatch l, long timeoutMs) {
+        long endTime = System.currentTimeMillis() + timeoutMs;
+        while (System.currentTimeMillis() < endTime) {
+            try {
+                l.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // keep waiting
+            }
+            if (l.getCount() <= 0) {
+                return;
+            }
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
new file mode 100644
index 0000000..0cb4cff
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephony;
+
+/**
+ * Allows applications to request the system to perform a network scan.
+ *
+ * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} will
+ * receive a NetworkScan which contains the callback method to stop the scan requested.
+ * @hide
+ */
+public class NetworkScan {
+
+    public static final String TAG = "NetworkScan";
+
+    public static final int SUCCESS = 0;
+    public static final int ERROR_INVALID_SCAN = 1;
+    public static final int ERROR_UNSUPPORTED = 2;
+    public static final int ERROR_INTERRUPTED = 3;
+    public static final int ERROR_CANCELLED = 4;
+
+    private final int mScanId;
+    private final int mSubId;
+
+    /**
+     * Stops the network scan
+     *
+     * This is the callback method to stop an ongoing scan. When user requests a new scan,
+     * a NetworkScan object will be returned, and the user can stop the scan by calling this
+     * method.
+     */
+    public void stop() throws RemoteException {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.stopNetworkScan(mSubId, mScanId);
+            } else {
+                throw new RemoteException("Failed to get the ITelephony instance.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "stopNetworkScan  RemoteException", ex);
+            throw new RemoteException("Failed to stop the network scan with id " + mScanId);
+        }
+    }
+
+    /**
+     * Creates a new NetworkScan with scanId
+     *
+     * @param scanId The id of the scan
+     * @param subId the id of the subscription
+     * @hide
+     */
+    public NetworkScan(int scanId, int subId) {
+        mScanId = scanId;
+        mSubId = subId;
+    }
+
+    private ITelephony getITelephony() {
+        return ITelephony.Stub.asInterface(
+            ServiceManager.getService(Context.TELEPHONY_SERVICE));
+    }
+}
diff --git a/telephony/java/android/telephony/NetworkScanRequest.aidl b/telephony/java/android/telephony/NetworkScanRequest.aidl
new file mode 100644
index 0000000..5addb1c
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkScanRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable NetworkScanRequest;
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
new file mode 100644
index 0000000..0a542a7
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -0,0 +1,115 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Defines a request to peform a network scan.
+ *
+ * This class defines whether the network scan will be performed only once or periodically until
+ * cancelled, when the scan is performed periodically, the time interval is not controlled by the
+ * user but defined by the modem vendor.
+ * @hide
+ */
+public final class NetworkScanRequest implements Parcelable {
+
+    /** Performs the scan only once */
+    public static final int SCAN_TYPE_ONE_SHOT = 0;
+    /**
+     * Performs the scan periodically until cancelled
+     *
+     * The modem will start new scans periodically, and the interval between two scans is usually
+     * multiple minutes.
+     * */
+    public static final int SCAN_TYPE_PERIODIC = 1;
+
+    /** Defines the type of the scan. */
+    public int scanType;
+
+    /** Describes the radio access technologies with bands or channels that need to be scanned. */
+    public RadioAccessSpecifier[] specifiers;
+
+    /**
+     * Creates a new NetworkScanRequest with scanType and network specifiers
+     *
+     * @param scanType The type of the scan
+     * @param specifiers the radio network with bands / channels to be scanned
+     */
+    public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers) {
+        this.scanType = scanType;
+        this.specifiers = specifiers;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(scanType);
+        dest.writeParcelableArray(specifiers, flags);
+    }
+
+    private NetworkScanRequest(Parcel in) {
+        scanType = in.readInt();
+        specifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
+                Object.class.getClassLoader(),
+                RadioAccessSpecifier.class);
+    }
+
+    @Override
+    public boolean equals (Object o) {
+        NetworkScanRequest nsr;
+
+        try {
+            nsr = (NetworkScanRequest) o;
+        } catch (ClassCastException ex) {
+            return false;
+        }
+
+        if (o == null) {
+            return false;
+        }
+
+        return (scanType == nsr.scanType
+                && Arrays.equals(specifiers, nsr.specifiers));
+    }
+
+    @Override
+    public int hashCode () {
+        return ((scanType * 31)
+                + (Arrays.hashCode(specifiers)) * 37);
+    }
+
+    public static final Creator<NetworkScanRequest> CREATOR =
+            new Creator<NetworkScanRequest>() {
+                @Override
+                public NetworkScanRequest createFromParcel(Parcel in) {
+                    return new NetworkScanRequest(in);
+                }
+
+                @Override
+                public NetworkScanRequest[] newArray(int size) {
+                    return new NetworkScanRequest[size];
+                }
+            };
+}
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.aidl b/telephony/java/android/telephony/RadioAccessSpecifier.aidl
new file mode 100644
index 0000000..7e09e0b
--- /dev/null
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable RadioAccessSpecifier;
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
new file mode 100644
index 0000000..33ce8b4
--- /dev/null
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Describes a particular radio access network to be scanned.
+ *
+ * The scan can be performed on either bands or channels for a specific radio access network type.
+ * @hide
+ */
+public final class RadioAccessSpecifier implements Parcelable {
+
+    /**
+     * The radio access network that needs to be scanned
+     *
+     * See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
+     */
+    public int radioAccessNetwork;
+
+    /**
+     * The frequency bands that need to be scanned
+     *
+     * bands must be used together with radioAccessNetwork
+     *
+     * See {@link RadioNetworkConstants} for details.
+     */
+    public int[] bands;
+
+    /**
+     * The frequency channels that need to be scanned
+     *
+     * channels must be used together with radioAccessNetwork
+     *
+     * See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
+     */
+    public int[] channels;
+
+    /**
+    * Creates a new RadioAccessSpecifier with radio network, bands and channels
+    *
+    * The user must specify the radio network type, and at least specify either of frequency
+    * bands or channels.
+    *
+    * @param ran The type of the radio access network
+    * @param bands the frequency bands to be scanned
+    * @param channels the frequency bands to be scanned
+    */
+    public RadioAccessSpecifier(int ran, int[] bands, int[] channels) {
+        this.radioAccessNetwork = ran;
+        this.bands = bands;
+        this.channels = channels;
+    }
+
+    public static final Parcelable.Creator<RadioAccessSpecifier> CREATOR =
+            new Parcelable.Creator<RadioAccessSpecifier> (){
+                @Override
+                public RadioAccessSpecifier createFromParcel(Parcel in) {
+                    return new RadioAccessSpecifier(in);
+                }
+
+                @Override
+                public RadioAccessSpecifier[] newArray(int size) {
+                    return new RadioAccessSpecifier[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(radioAccessNetwork);
+        dest.writeIntArray(bands);
+        dest.writeIntArray(channels);
+    }
+
+    private RadioAccessSpecifier(Parcel in) {
+        radioAccessNetwork = in.readInt();
+        bands = in.createIntArray();
+        channels = in.createIntArray();
+    }
+
+    @Override
+    public boolean equals (Object o) {
+        RadioAccessSpecifier ras;
+
+        try {
+            ras = (RadioAccessSpecifier) o;
+        } catch (ClassCastException ex) {
+            return false;
+        }
+
+        if (o == null) {
+            return false;
+        }
+
+        return (radioAccessNetwork == ras.radioAccessNetwork
+                && Arrays.equals(bands, ras.bands)
+                && Arrays.equals(channels, ras.channels));
+    }
+
+    @Override
+    public int hashCode () {
+        return ((radioAccessNetwork * 31)
+                + (Arrays.hashCode(bands) * 37)
+                + (Arrays.hashCode(channels)) * 39);
+    }
+}
diff --git a/telephony/java/android/telephony/RadioNetworkConstants.java b/telephony/java/android/telephony/RadioNetworkConstants.java
new file mode 100644
index 0000000..1a9072d
--- /dev/null
+++ b/telephony/java/android/telephony/RadioNetworkConstants.java
@@ -0,0 +1,169 @@
+/*
+ * 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;
+
+/**
+ * Contains radio access network related constants.
+ * @hide
+ */
+public final class RadioNetworkConstants {
+
+    public static final class RadioAccessNetworks {
+        public static final int GERAN = 1;
+        public static final int UTRAN = 2;
+        public static final int EUTRAN = 3;
+        /** @hide */
+        public static final int CDMA2000 = 4;
+    }
+
+    /**
+     * Frenquency bands for GERAN.
+     * http://www.etsi.org/deliver/etsi_ts/145000_145099/145005/14.00.00_60/ts_145005v140000p.pdf
+     */
+    public static final class GeranBands {
+        public static final int BAND_T380 = 1;
+        public static final int BAND_T410 = 2;
+        public static final int BAND_450 = 3;
+        public static final int BAND_480 = 4;
+        public static final int BAND_710 = 5;
+        public static final int BAND_750 = 6;
+        public static final int BAND_T810 = 7;
+        public static final int BAND_850 = 8;
+        public static final int BAND_P900 = 9;
+        public static final int BAND_E900 = 10;
+        public static final int BAND_R900 = 11;
+        public static final int BAND_DCS1800 = 12;
+        public static final int BAND_PCS1900 = 13;
+        public static final int BAND_ER900 = 14;
+    }
+
+    /**
+     * Frenquency bands for UTRAN.
+     * http://www.etsi.org/deliver/etsi_ts/125100_125199/125104/13.03.00_60/ts_125104v130p.pdf
+     */
+    public static final class UtranBands {
+        public static final int BAND_1 = 1;
+        public static final int BAND_2 = 2;
+        public static final int BAND_3 = 3;
+        public static final int BAND_4 = 4;
+        public static final int BAND_5 = 5;
+        public static final int BAND_6 = 6;
+        public static final int BAND_7 = 7;
+        public static final int BAND_8 = 8;
+        public static final int BAND_9 = 9;
+        public static final int BAND_10 = 10;
+        public static final int BAND_11 = 11;
+        public static final int BAND_12 = 12;
+        public static final int BAND_13 = 13;
+        public static final int BAND_14 = 14;
+        /** band 15, 16, 17, 18 are reserved */
+        public static final int BAND_19 = 19;
+        public static final int BAND_20 = 20;
+        public static final int BAND_21 = 21;
+        public static final int BAND_22 = 22;
+        /** band 23, 24 are reserved */
+        public static final int BAND_25 = 25;
+        public static final int BAND_26 = 26;
+    }
+
+    /**
+     * Frenquency bands for EUTRAN.
+     * http://www.etsi.org/deliver/etsi_ts/136100_136199/136101/14.03.00_60/ts_136101v140p.pdf
+     */
+    public static final class EutranBands {
+        public static final int BAND_1 = 1;
+        public static final int BAND_2 = 2;
+        public static final int BAND_3 = 3;
+        public static final int BAND_4 = 4;
+        public static final int BAND_5 = 5;
+        public static final int BAND_6 = 6;
+        public static final int BAND_7 = 7;
+        public static final int BAND_8 = 8;
+        public static final int BAND_9 = 9;
+        public static final int BAND_10 = 10;
+        public static final int BAND_11 = 11;
+        public static final int BAND_12 = 12;
+        public static final int BAND_13 = 13;
+        public static final int BAND_14 = 14;
+        public static final int BAND_17 = 17;
+        public static final int BAND_18 = 18;
+        public static final int BAND_19 = 19;
+        public static final int BAND_20 = 20;
+        public static final int BAND_21 = 21;
+        public static final int BAND_22 = 22;
+        public static final int BAND_23 = 23;
+        public static final int BAND_24 = 24;
+        public static final int BAND_25 = 25;
+        public static final int BAND_26 = 26;
+        public static final int BAND_27 = 27;
+        public static final int BAND_28 = 28;
+        public static final int BAND_30 = 30;
+        public static final int BAND_31 = 31;
+        public static final int BAND_33 = 33;
+        public static final int BAND_34 = 34;
+        public static final int BAND_35 = 35;
+        public static final int BAND_36 = 36;
+        public static final int BAND_37 = 37;
+        public static final int BAND_38 = 38;
+        public static final int BAND_39 = 39;
+        public static final int BAND_40 = 40;
+        public static final int BAND_41 = 41;
+        public static final int BAND_42 = 42;
+        public static final int BAND_43 = 43;
+        public static final int BAND_44 = 44;
+        public static final int BAND_45 = 45;
+        public static final int BAND_46 = 46;
+        public static final int BAND_47 = 47;
+        public static final int BAND_48 = 48;
+        public static final int BAND_65 = 65;
+        public static final int BAND_66 = 66;
+        public static final int BAND_68 = 68;
+        public static final int BAND_70 = 70;
+    }
+
+    /**
+     * Frenquency bands for CDMA2000.
+     * http://www.3gpp2.org/Public_html/Specs/C.S0057-E_v1.0_Bandclass_Specification.pdf
+     * @hide
+     *
+     * TODO(yinxu): Check with the nexus team about the definition of CDMA bands.
+     */
+    public static final class CdmaBands {
+        public static final int BAND_0 = 1;
+        public static final int BAND_1 = 2;
+        public static final int BAND_2 = 3;
+        public static final int BAND_3 = 4;
+        public static final int BAND_4 = 5;
+        public static final int BAND_5 = 6;
+        public static final int BAND_6 = 7;
+        public static final int BAND_7 = 8;
+        public static final int BAND_8 = 9;
+        public static final int BAND_9 = 10;
+        public static final int BAND_10 = 11;
+        public static final int BAND_11 = 12;
+        public static final int BAND_12 = 13;
+        public static final int BAND_13 = 14;
+        public static final int BAND_14 = 15;
+        public static final int BAND_15 = 16;
+        public static final int BAND_16 = 17;
+        public static final int BAND_17 = 18;
+        public static final int BAND_18 = 19;
+        public static final int BAND_19 = 20;
+        public static final int BAND_20 = 21;
+        public static final int BAND_21 = 22;
+    }
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
new file mode 100644
index 0000000..07bcb38
--- /dev/null
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -0,0 +1,1680 @@
+/*
+ * 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.telephony;
+
+import android.annotation.SystemApi;
+import android.app.ActivityThread;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.telephony.IMms;
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsRawData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * TODO(code review): Curious question... Why are a lot of these
+ * methods not declared as static, since they do not seem to require
+ * any local object state?  Presumably this cannot be changed without
+ * interfering with the API...
+ */
+
+/**
+ * Manages SMS operations such as sending data, text, and pdu SMS messages.
+ * Get this object by calling the static method {@link #getDefault()}.
+ *
+ * <p>For information about how to behave as the default SMS app on Android 4.4 (API level 19)
+ * and higher, see {@link android.provider.Telephony}.
+ */
+public final class SmsManager {
+    private static final String TAG = "SmsManager";
+    /**
+     * A psuedo-subId that represents the default subId at any given time. The actual subId it
+     * represents changes as the default subId is changed.
+     */
+    private static final int DEFAULT_SUBSCRIPTION_ID = -1002;
+
+    /** Singleton object constructed during class initialization. */
+    private static final SmsManager sInstance = new SmsManager(DEFAULT_SUBSCRIPTION_ID);
+    private static final Object sLockObject = new Object();
+
+    /** @hide */
+    public static final int CELL_BROADCAST_RAN_TYPE_GSM = 0;
+    /** @hide */
+    public static final int CELL_BROADCAST_RAN_TYPE_CDMA = 1;
+
+    /** SMS record length from TS 51.011 10.5.3
+     * @hide
+     */
+    public static final int SMS_RECORD_LENGTH = 176;
+
+    /** SMS record length from C.S0023 3.4.27
+     * @hide
+     */
+    public static final int CDMA_SMS_RECORD_LENGTH = 255;
+
+    private static final Map<Integer, SmsManager> sSubInstances =
+            new ArrayMap<Integer, SmsManager>();
+
+    /** A concrete subscription id, or the pseudo DEFAULT_SUBSCRIPTION_ID */
+    private int mSubId;
+
+    /*
+     * Key for the various carrier-dependent configuration values.
+     * Some of the values are used by the system in processing SMS or MMS messages. Others
+     * are provided for the convenience of SMS applications.
+     */
+
+    /**
+     * Whether to append transaction id to MMS WAP Push M-Notification.ind's content location URI
+     * when constructing the download URL of a new MMS (boolean type)
+     */
+    public static final String MMS_CONFIG_APPEND_TRANSACTION_ID =
+            CarrierConfigManager.KEY_MMS_APPEND_TRANSACTION_ID_BOOL;
+    /**
+     * Whether MMS is enabled for the current carrier (boolean type)
+     */
+    public static final String
+        MMS_CONFIG_MMS_ENABLED = CarrierConfigManager.KEY_MMS_MMS_ENABLED_BOOL;
+    /**
+     * Whether group MMS is enabled for the current carrier (boolean type)
+     */
+    public static final String
+            MMS_CONFIG_GROUP_MMS_ENABLED = CarrierConfigManager.KEY_MMS_GROUP_MMS_ENABLED_BOOL;
+    /**
+     * If this is enabled, M-NotifyResp.ind should be sent to the WAP Push content location instead
+     * of the default MMSC (boolean type)
+     */
+    public static final String MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED =
+            CarrierConfigManager.KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL;
+    /**
+     * Whether alias is enabled (boolean type)
+     */
+    public static final String
+            MMS_CONFIG_ALIAS_ENABLED = CarrierConfigManager.KEY_MMS_ALIAS_ENABLED_BOOL;
+    /**
+     * Whether audio is allowed to be attached for MMS messages (boolean type)
+     */
+    public static final String
+            MMS_CONFIG_ALLOW_ATTACH_AUDIO = CarrierConfigManager.KEY_MMS_ALLOW_ATTACH_AUDIO_BOOL;
+    /**
+     * Whether multipart SMS is enabled (boolean type)
+     */
+    public static final String MMS_CONFIG_MULTIPART_SMS_ENABLED =
+            CarrierConfigManager.KEY_MMS_MULTIPART_SMS_ENABLED_BOOL;
+    /**
+     * Whether SMS delivery report is enabled (boolean type)
+     */
+    public static final String MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED =
+            CarrierConfigManager.KEY_MMS_SMS_DELIVERY_REPORT_ENABLED_BOOL;
+    /**
+     * Whether content-disposition field should be expected in an MMS PDU (boolean type)
+     */
+    public static final String MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION =
+            CarrierConfigManager.KEY_MMS_SUPPORT_MMS_CONTENT_DISPOSITION_BOOL;
+    /**
+     * Whether multipart SMS should be sent as separate messages
+     */
+    public static final String MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES =
+            CarrierConfigManager.KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL;
+    /**
+     * Whether MMS read report is enabled (boolean type)
+     */
+    public static final String MMS_CONFIG_MMS_READ_REPORT_ENABLED =
+            CarrierConfigManager.KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL;
+    /**
+     * Whether MMS delivery report is enabled (boolean type)
+     */
+    public static final String MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED =
+            CarrierConfigManager.KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL;
+    /**
+     * Max MMS message size in bytes (int type)
+     */
+    public static final String
+            MMS_CONFIG_MAX_MESSAGE_SIZE = CarrierConfigManager.KEY_MMS_MAX_MESSAGE_SIZE_INT;
+    /**
+     * Max MMS image width (int type)
+     */
+    public static final String
+            MMS_CONFIG_MAX_IMAGE_WIDTH = CarrierConfigManager.KEY_MMS_MAX_IMAGE_WIDTH_INT;
+    /**
+     * Max MMS image height (int type)
+     */
+    public static final String
+            MMS_CONFIG_MAX_IMAGE_HEIGHT = CarrierConfigManager.KEY_MMS_MAX_IMAGE_HEIGHT_INT;
+    /**
+     * Limit of recipients of MMS messages (int type)
+     */
+    public static final String
+            MMS_CONFIG_RECIPIENT_LIMIT = CarrierConfigManager.KEY_MMS_RECIPIENT_LIMIT_INT;
+    /**
+     * Min alias character count (int type)
+     */
+    public static final String
+            MMS_CONFIG_ALIAS_MIN_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MIN_CHARS_INT;
+    /**
+     * Max alias character count (int type)
+     */
+    public static final String
+            MMS_CONFIG_ALIAS_MAX_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MAX_CHARS_INT;
+    /**
+     * When the number of parts of a multipart SMS reaches this threshold, it should be converted
+     * into an MMS (int type)
+     */
+    public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD =
+            CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT;
+    /**
+     * Some carriers require SMS to be converted into MMS when text length reaches this threshold
+     * (int type)
+     */
+    public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD =
+            CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT;
+    /**
+     * Max message text size (int type)
+     */
+    public static final String MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE =
+            CarrierConfigManager.KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT;
+    /**
+     * Max message subject length (int type)
+     */
+    public static final String
+            MMS_CONFIG_SUBJECT_MAX_LENGTH = CarrierConfigManager.KEY_MMS_SUBJECT_MAX_LENGTH_INT;
+    /**
+     * MMS HTTP socket timeout in milliseconds (int type)
+     */
+    public static final String
+            MMS_CONFIG_HTTP_SOCKET_TIMEOUT = CarrierConfigManager.KEY_MMS_HTTP_SOCKET_TIMEOUT_INT;
+    /**
+     * The name of the UA Prof URL HTTP header for MMS HTTP request (String type)
+     */
+    public static final String
+            MMS_CONFIG_UA_PROF_TAG_NAME = CarrierConfigManager.KEY_MMS_UA_PROF_TAG_NAME_STRING;
+    /**
+     * The User-Agent header value for MMS HTTP request (String type)
+     */
+    public static final String
+            MMS_CONFIG_USER_AGENT = CarrierConfigManager.KEY_MMS_USER_AGENT_STRING;
+    /**
+     * The UA Profile URL header value for MMS HTTP request (String type)
+     */
+    public static final String
+            MMS_CONFIG_UA_PROF_URL = CarrierConfigManager.KEY_MMS_UA_PROF_URL_STRING;
+    /**
+     * A list of HTTP headers to add to MMS HTTP request, separated by "|" (String type)
+     */
+    public static final String
+            MMS_CONFIG_HTTP_PARAMS = CarrierConfigManager.KEY_MMS_HTTP_PARAMS_STRING;
+    /**
+     * Email gateway number (String type)
+     */
+    public static final String MMS_CONFIG_EMAIL_GATEWAY_NUMBER =
+            CarrierConfigManager.KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING;
+    /**
+     * The suffix to append to the NAI header value for MMS HTTP request (String type)
+     */
+    public static final String
+            MMS_CONFIG_NAI_SUFFIX = CarrierConfigManager.KEY_MMS_NAI_SUFFIX_STRING;
+    /**
+     * If true, show the cell broadcast (amber alert) in the SMS settings. Some carriers don't want
+     * this shown. (Boolean type)
+     */
+    public static final String MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS =
+            CarrierConfigManager.KEY_MMS_SHOW_CELL_BROADCAST_APP_LINKS_BOOL;
+    /**
+     * Whether the carrier MMSC supports charset field in Content-Type header. If this is false,
+     * then we don't add "charset" to "Content-Type"
+     */
+    public static final String MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER =
+            CarrierConfigManager.KEY_MMS_SUPPORT_HTTP_CHARSET_HEADER_BOOL;
+    /**
+     * If true, add "Connection: close" header to MMS HTTP requests so the connection
+     * is immediately closed (disabling keep-alive). (Boolean type)
+     * @hide
+     */
+    public static final String MMS_CONFIG_CLOSE_CONNECTION =
+            CarrierConfigManager.KEY_MMS_CLOSE_CONNECTION_BOOL;
+
+    /*
+     * Forwarded constants from SimDialogActivity.
+     */
+    private static String DIALOG_TYPE_KEY = "dialog_type";
+    private static final int SMS_PICK = 2;
+
+    /**
+     * Send a text based SMS.
+     *
+     * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+     * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+     *
+     * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+     * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+     * writes messages sent using this method to the SMS Provider (the default SMS app is always
+     * responsible for writing its sent messages to the SMS Provider). For information about
+     * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+     *
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *  the current default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     *
+     * @throws IllegalArgumentException if destinationAddress or text are empty
+     */
+    public void sendTextMessage(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                true /* persistMessage*/);
+    }
+
+    private void sendTextMessageInternal(String destinationAddress, String scAddress,
+            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean persistMessage) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+
+        if (TextUtils.isEmpty(text)) {
+            throw new IllegalArgumentException("Invalid message body");
+        }
+
+        try {
+            ISms iccISms = getISmsServiceOrThrow();
+            iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
+                    destinationAddress,
+                    scAddress, text, sentIntent, deliveryIntent,
+                    persistMessage);
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Send a text based SMS without writing it into the SMS Provider.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+     * privileges.
+     * </p>
+     *
+     * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
+     * @hide
+     */
+    @SystemApi
+    public void sendTextMessageWithoutPersisting(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                false /* persistMessage */);
+    }
+
+    /**
+     * A variant of {@link SmsManager#sendTextMessage} that allows self to be the caller. This is
+     * for internal use only.
+     *
+     * @param persistMessage whether to persist the sent message in the SMS app. the caller must be
+     * the Phone process if set to false.
+     *
+     * @hide
+     */
+    public void sendTextMessageWithSelfPermissions(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+
+        if (TextUtils.isEmpty(text)) {
+            throw new IllegalArgumentException("Invalid message body");
+        }
+
+        try {
+            ISms iccISms = getISmsServiceOrThrow();
+            iccISms.sendTextForSubscriberWithSelfPermissions(getSubscriptionId(),
+                    ActivityThread.currentPackageName(),
+                    destinationAddress,
+                    scAddress, text, sentIntent, deliveryIntent, persistMessage);
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Inject an SMS PDU into the android application framework.
+     *
+     * The caller should have carrier privileges.
+     * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+     *
+     * @param pdu is the byte array of pdu to be injected into android application framework
+     * @param format is the format of SMS pdu (3gpp or 3gpp2)
+     * @param receivedIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully received by the
+     *  android application framework, or failed. This intent is broadcasted at
+     *  the same time an SMS received from radio is acknowledged back.
+     *  The result code will be <code>RESULT_SMS_HANDLED</code> for success, or
+     *  <code>RESULT_SMS_GENERIC_ERROR</code> for error.
+     *
+     * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2.
+     */
+    public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
+        if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
+            // Format must be either 3gpp or 3gpp2.
+            throw new IllegalArgumentException(
+                    "Invalid pdu format. format must be either 3gpp or 3gpp2");
+        }
+        try {
+            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            if (iccISms != null) {
+                iccISms.injectSmsPduForSubscriber(
+                        getSubscriptionId(), pdu, format, receivedIntent);
+            }
+        } catch (RemoteException ex) {
+          // ignore it
+        }
+    }
+
+    /**
+     * Divide a message text into several fragments, none bigger than
+     * the maximum SMS message size.
+     *
+     * @param text the original message.  Must not be null.
+     * @return an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original message
+     *
+     * @throws IllegalArgumentException if text is null
+     */
+    public ArrayList<String> divideMessage(String text) {
+        if (null == text) {
+            throw new IllegalArgumentException("text is null");
+        }
+        return SmsMessage.fragmentText(text);
+    }
+
+    /**
+     * Send a multi-part text based SMS.  The callee should have already
+     * divided the message into correctly sized parts by calling
+     * <code>divideMessage</code>.
+     *
+     * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+     * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+     *
+     * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+     * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+     * writes messages sent using this method to the SMS Provider (the default SMS app is always
+     * responsible for writing its sent messages to the SMS Provider). For information about
+     * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param parts an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original message
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK</code> for success,
+     *   or one of these errors:<br>
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *   <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *   <code>RESULT_ERROR_NULL_PDU</code><br>
+     *   For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+     *   the extra "errorCode" containing a radio technology specific value,
+     *   generally only useful for troubleshooting.<br>
+     *   The per-application based SMS control checks sentIntent. If sentIntent
+     *   is NULL the caller will be checked against all unknown applications,
+     *   which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     *
+     * @throws IllegalArgumentException if destinationAddress or data are empty
+     */
+    public void sendMultipartTextMessage(
+            String destinationAddress, String scAddress, ArrayList<String> parts,
+            ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, true /* persistMessage*/);
+    }
+
+    private void sendMultipartTextMessageInternal(
+            String destinationAddress, String scAddress, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            boolean persistMessage) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+        if (parts == null || parts.size() < 1) {
+            throw new IllegalArgumentException("Invalid message body");
+        }
+
+        if (parts.size() > 1) {
+            try {
+                ISms iccISms = getISmsServiceOrThrow();
+                iccISms.sendMultipartTextForSubscriber(getSubscriptionId(),
+                        ActivityThread.currentPackageName(),
+                        destinationAddress, scAddress, parts,
+                        sentIntents, deliveryIntents, persistMessage);
+            } catch (RemoteException ex) {
+                // ignore it
+            }
+        } else {
+            PendingIntent sentIntent = null;
+            PendingIntent deliveryIntent = null;
+            if (sentIntents != null && sentIntents.size() > 0) {
+                sentIntent = sentIntents.get(0);
+            }
+            if (deliveryIntents != null && deliveryIntents.size() > 0) {
+                deliveryIntent = deliveryIntents.get(0);
+            }
+            sendTextMessage(destinationAddress, scAddress, parts.get(0),
+                    sentIntent, deliveryIntent);
+        }
+    }
+
+    /**
+     * Send a multi-part text based SMS without writing it into the SMS Provider.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+     * privileges.
+     * </p>
+     *
+     * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList)
+     * @hide
+     **/
+    @SystemApi
+    public void sendMultipartTextMessageWithoutPersisting(
+            String destinationAddress, String scAddress, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, false /* persistMessage*/);
+    }
+
+    /**
+     * Send a data based SMS to a specific application port.
+     *
+     * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+     * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *  the current default SMSC
+     * @param destinationPort the port to deliver the message to
+     * @param data the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     *
+     * @throws IllegalArgumentException if destinationAddress or data are empty
+     */
+    public void sendDataMessage(
+            String destinationAddress, String scAddress, short destinationPort,
+            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+
+        if (data == null || data.length == 0) {
+            throw new IllegalArgumentException("Invalid message data");
+        }
+
+        try {
+            ISms iccISms = getISmsServiceOrThrow();
+            iccISms.sendDataForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
+                    destinationAddress, scAddress, destinationPort & 0xFFFF,
+                    data, sentIntent, deliveryIntent);
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * A variant of {@link SmsManager#sendDataMessage} that allows self to be the caller. This is
+     * for internal use only.
+     *
+     * @hide
+     */
+    public void sendDataMessageWithSelfPermissions(
+            String destinationAddress, String scAddress, short destinationPort,
+            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+
+        if (data == null || data.length == 0) {
+            throw new IllegalArgumentException("Invalid message data");
+        }
+
+        try {
+            ISms iccISms = getISmsServiceOrThrow();
+            iccISms.sendDataForSubscriberWithSelfPermissions(getSubscriptionId(),
+                    ActivityThread.currentPackageName(), destinationAddress, scAddress,
+                    destinationPort & 0xFFFF, data, sentIntent, deliveryIntent);
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+
+
+    /**
+     * Get the SmsManager associated with the default subscription id. The instance will always be
+     * associated with the default subscription id, even if the default subscription id is changed.
+     *
+     * @return the SmsManager associated with the default subscription id
+     */
+    public static SmsManager getDefault() {
+        return sInstance;
+    }
+
+    /**
+     * Get the the instance of the SmsManager associated with a particular subscription id
+     *
+     * @param subId an SMS subscription id, typically accessed using
+     *   {@link android.telephony.SubscriptionManager}
+     * @return the instance of the SmsManager associated with subId
+     */
+    public static SmsManager getSmsManagerForSubscriptionId(int subId) {
+        // TODO(shri): Add javadoc link once SubscriptionManager is made public api
+        synchronized(sLockObject) {
+            SmsManager smsManager = sSubInstances.get(subId);
+            if (smsManager == null) {
+                smsManager = new SmsManager(subId);
+                sSubInstances.put(subId, smsManager);
+            }
+            return smsManager;
+        }
+    }
+
+    private SmsManager(int subId) {
+        mSubId = subId;
+    }
+
+    /**
+     * Get the associated subscription id. If the instance was returned by {@link #getDefault()},
+     * then this method may return different values at different points in time (if the user
+     * changes the default subscription id). It will return < 0 if the default subscription id
+     * cannot be determined.
+     *
+     * Additionally, to support legacy applications that are not multi-SIM aware,
+     * if the following are true:
+     *     - We are using a multi-SIM device
+     *     - A default SMS SIM has not been selected
+     *     - At least one SIM subscription is available
+     * then ask the user to set the default SMS SIM.
+     *
+     * @return associated subscription id
+     */
+    public int getSubscriptionId() {
+        final int subId = (mSubId == DEFAULT_SUBSCRIPTION_ID)
+                ? getDefaultSmsSubscriptionId() : mSubId;
+        boolean isSmsSimPickActivityNeeded = false;
+        final Context context = ActivityThread.currentApplication().getApplicationContext();
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                isSmsSimPickActivityNeeded = iccISms.isSmsSimPickActivityNeeded(subId);
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Exception in getSubscriptionId");
+        }
+
+        if (isSmsSimPickActivityNeeded) {
+            Log.d(TAG, "getSubscriptionId isSmsSimPickActivityNeeded is true");
+            // ask the user for a default SMS SIM.
+            Intent intent = new Intent();
+            intent.setClassName("com.android.settings",
+                    "com.android.settings.sim.SimDialogActivity");
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.putExtra(DIALOG_TYPE_KEY, SMS_PICK);
+            try {
+                context.startActivity(intent);
+            } catch (ActivityNotFoundException anfe) {
+                // If Settings is not installed, only log the error as we do not want to break
+                // legacy applications.
+                Log.e(TAG, "Unable to launch Settings application.");
+            }
+        }
+
+        return subId;
+    }
+
+    /**
+     * Returns the ISms service, or throws an UnsupportedOperationException if
+     * the service does not exist.
+     */
+    private static ISms getISmsServiceOrThrow() {
+        ISms iccISms = getISmsService();
+        if (iccISms == null) {
+            throw new UnsupportedOperationException("Sms is not supported");
+        }
+        return iccISms;
+    }
+
+    private static ISms getISmsService() {
+        return ISms.Stub.asInterface(ServiceManager.getService("isms"));
+    }
+
+    /**
+     * Copy a raw SMS PDU to the ICC.
+     * ICC (Integrated Circuit Card) is the card of the device.
+     * For example, this can be the SIM or USIM for GSM.
+     *
+     * @param smsc the SMSC for this message, or NULL for the default SMSC
+     * @param pdu the raw PDU to store
+     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
+     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+     * @return true for success
+     *
+     * @throws IllegalArgumentException if pdu is NULL
+     * {@hide}
+     */
+    public boolean copyMessageToIcc(byte[] smsc, byte[] pdu,int status) {
+        boolean success = false;
+
+        if (null == pdu) {
+            throw new IllegalArgumentException("pdu is NULL");
+        }
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                success = iccISms.copyMessageToIccEfForSubscriber(getSubscriptionId(),
+                        ActivityThread.currentPackageName(),
+                        status, pdu, smsc);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
+     * Delete the specified message from the ICC.
+     * ICC (Integrated Circuit Card) is the card of the device.
+     * For example, this can be the SIM or USIM for GSM.
+     *
+     * @param messageIndex is the record index of the message on ICC
+     * @return true for success
+     *
+     * {@hide}
+     */
+    public boolean
+    deleteMessageFromIcc(int messageIndex) {
+        boolean success = false;
+        byte[] pdu = new byte[SMS_RECORD_LENGTH-1];
+        Arrays.fill(pdu, (byte)0xff);
+
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
+                        ActivityThread.currentPackageName(),
+                        messageIndex, STATUS_ON_ICC_FREE, pdu);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
+     * Update the specified message on the ICC.
+     * ICC (Integrated Circuit Card) is the card of the device.
+     * For example, this can be the SIM or USIM for GSM.
+     *
+     * @param messageIndex record index of message to update
+     * @param newStatus new message status (STATUS_ON_ICC_READ,
+     *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
+     *                  STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
+     * @param pdu the raw PDU to store
+     * @return true for success
+     *
+     * {@hide}
+     */
+    public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
+        boolean success = false;
+
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
+                        ActivityThread.currentPackageName(),
+                        messageIndex, newStatus, pdu);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
+     * Retrieves all messages currently stored on ICC.
+     * ICC (Integrated Circuit Card) is the card of the device.
+     * For example, this can be the SIM or USIM for GSM.
+     *
+     * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
+     *
+     * {@hide}
+     */
+    public ArrayList<SmsMessage> getAllMessagesFromIcc() {
+        List<SmsRawData> records = null;
+
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                records = iccISms.getAllMessagesFromIccEfForSubscriber(
+                        getSubscriptionId(),
+                        ActivityThread.currentPackageName());
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return createMessageListFromRawRecords(records);
+    }
+
+    /**
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier and RAN type. The RAN type specify this message ID
+     * belong to 3GPP (GSM) or 3GPP2(CDMA).Note that if two different clients
+     * enable the same message identifier, they must both disable it for the device to stop
+     * receiving those messages. All received messages will be broadcast in an
+     * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
+     * or C.R1001-G (3GPP2)
+     * @param ranType as defined in class SmsManager, the value can be one of these:
+     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @return true if successful, false otherwise
+     * @see #disableCellBroadcast(int, int)
+     *
+     * {@hide}
+     */
+    public boolean enableCellBroadcast(int messageIdentifier, int ranType) {
+        boolean success = false;
+
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                success = iccISms.enableCellBroadcastForSubscriber(
+                        getSubscriptionId(), messageIdentifier, ranType);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
+     * Disable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier and RAN type. The RAN type specify this message ID
+     * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients
+     * enable the same message identifier, they must both disable it for the
+     * device to stop receiving those messages.
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
+     * or C.R1001-G (3GPP2)
+     * @param ranType as defined in class SmsManager, the value can be one of these:
+     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcast(int, int)
+     *
+     * {@hide}
+     */
+    public boolean disableCellBroadcast(int messageIdentifier, int ranType) {
+        boolean success = false;
+
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                success = iccISms.disableCellBroadcastForSubscriber(
+                        getSubscriptionId(), messageIdentifier, ranType);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range and RAN type. The RAN type specify this message ID
+     * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
+     * the same message identifier, they must both disable it for the device to stop
+     * receiving those messages. All received messages will be broadcast in an
+     * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
+     * or C.R1001-G (3GPP2)
+     * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
+     * or C.R1001-G (3GPP2)
+     * @param ranType as defined in class SmsManager, the value can be one of these:
+     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @return true if successful, false otherwise
+     * @see #disableCellBroadcastRange(int, int, int)
+     *
+     * @throws IllegalArgumentException if endMessageId < startMessageId
+     * {@hide}
+     */
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+        boolean success = false;
+
+        if (endMessageId < startMessageId) {
+            throw new IllegalArgumentException("endMessageId < startMessageId");
+        }
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                success = iccISms.enableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+                        startMessageId, endMessageId, ranType);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
+     * Disable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range and RAN type. The RAN type specify this message
+     * ID range belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different
+     * clients enable the same message identifier, they must both disable it for
+     * the device to stop receiving those messages.
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
+     * or C.R1001-G (3GPP2)
+     * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
+     * or C.R1001-G (3GPP2)
+     * @param ranType as defined in class SmsManager, the value can be one of these:
+     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcastRange(int, int, int)
+     *
+     * @throws IllegalArgumentException if endMessageId < startMessageId
+     * {@hide}
+     */
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+        boolean success = false;
+
+        if (endMessageId < startMessageId) {
+            throw new IllegalArgumentException("endMessageId < startMessageId");
+        }
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                success = iccISms.disableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+                        startMessageId, endMessageId, ranType);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+
+        return success;
+    }
+
+    /**
+     * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
+     * records returned by <code>getAllMessagesFromIcc()</code>
+     *
+     * @param records SMS EF records, returned by
+     *   <code>getAllMessagesFromIcc</code>
+     * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
+     */
+    private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+        ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
+        if (records != null) {
+            int count = records.size();
+            for (int i = 0; i < count; i++) {
+                SmsRawData data = records.get(i);
+                // List contains all records, including "free" records (null)
+                if (data != null) {
+                    SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+                    if (sms != null) {
+                        messages.add(sms);
+                    }
+                }
+            }
+        }
+        return messages;
+    }
+
+    /**
+     * SMS over IMS is supported if IMS is registered and SMS is supported
+     * on IMS.
+     *
+     * @return true if SMS over IMS is supported, false otherwise
+     *
+     * @see #getImsSmsFormat()
+     *
+     * @hide
+     */
+    public boolean isImsSmsSupported() {
+        boolean boSupported = false;
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                boSupported = iccISms.isImsSmsSupportedForSubscriber(getSubscriptionId());
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return boSupported;
+    }
+
+    /**
+     * Gets SMS format supported on IMS.  SMS over IMS format is
+     * either 3GPP or 3GPP2.
+     *
+     * @return SmsMessage.FORMAT_3GPP,
+     *         SmsMessage.FORMAT_3GPP2
+     *      or SmsMessage.FORMAT_UNKNOWN
+     *
+     * @see #isImsSmsSupported()
+     *
+     * @hide
+     */
+    public String getImsSmsFormat() {
+        String format = com.android.internal.telephony.SmsConstants.FORMAT_UNKNOWN;
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                format = iccISms.getImsSmsFormatForSubscriber(getSubscriptionId());
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return format;
+    }
+
+    /**
+     * Get default sms subscription id
+     *
+     * @return the default SMS subscription id
+     */
+    public static int getDefaultSmsSubscriptionId() {
+        ISms iccISms = null;
+        try {
+            iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            return iccISms.getPreferredSmsSubscription();
+        } catch (RemoteException ex) {
+            return -1;
+        } catch (NullPointerException ex) {
+            return -1;
+        }
+    }
+
+    /**
+     * Get SMS prompt property,  enabled or not
+     *
+     * @return true if enabled, false otherwise
+     * @hide
+     */
+    public boolean isSMSPromptEnabled() {
+        ISms iccISms = null;
+        try {
+            iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            return iccISms.isSMSPromptEnabled();
+        } catch (RemoteException ex) {
+            return false;
+        } catch (NullPointerException ex) {
+            return false;
+        }
+    }
+
+    // see SmsMessage.getStatusOnIcc
+
+    /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+    static public final int STATUS_ON_ICC_FREE      = 0;
+
+    /** Received and read (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+    static public final int STATUS_ON_ICC_READ      = 1;
+
+    /** Received and unread (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+    static public final int STATUS_ON_ICC_UNREAD    = 3;
+
+    /** Stored and sent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+    static public final int STATUS_ON_ICC_SENT      = 5;
+
+    /** Stored and unsent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+    static public final int STATUS_ON_ICC_UNSENT    = 7;
+
+    // SMS send failure result codes
+
+    /** Generic failure cause */
+    static public final int RESULT_ERROR_GENERIC_FAILURE    = 1;
+    /** Failed because radio was explicitly turned off */
+    static public final int RESULT_ERROR_RADIO_OFF          = 2;
+    /** Failed because no pdu provided */
+    static public final int RESULT_ERROR_NULL_PDU           = 3;
+    /** Failed because service is currently unavailable */
+    static public final int RESULT_ERROR_NO_SERVICE         = 4;
+    /** Failed because we reached the sending queue limit.  {@hide} */
+    static public final int RESULT_ERROR_LIMIT_EXCEEDED     = 5;
+    /** Failed because FDN is enabled. {@hide} */
+    static public final int RESULT_ERROR_FDN_CHECK_FAILURE  = 6;
+
+    static private final String PHONE_PACKAGE_NAME = "com.android.phone";
+
+    /**
+     * Send an MMS message
+     *
+     * @param context application context
+     * @param contentUri the content Uri from which the message pdu will be read
+     * @param locationUrl the optional location url where message should be sent to
+     * @param configOverrides the carrier-specific messaging configuration values to override for
+     *  sending the message.
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed
+     * @throws IllegalArgumentException if contentUri is empty
+     */
+    public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
+            Bundle configOverrides, PendingIntent sentIntent) {
+        if (contentUri == null) {
+            throw new IllegalArgumentException("Uri contentUri null");
+        }
+        try {
+            final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms == null) {
+                return;
+            }
+
+            iMms.sendMessage(getSubscriptionId(), ActivityThread.currentPackageName(), contentUri,
+                    locationUrl, configOverrides, sentIntent);
+        } catch (RemoteException e) {
+            // Ignore it
+        }
+    }
+
+    /**
+     * Download an MMS message from carrier by a given location URL
+     *
+     * @param context application context
+     * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
+     *  from the MMS WAP push notification
+     * @param contentUri the content uri to which the downloaded pdu will be written
+     * @param configOverrides the carrier-specific messaging configuration values to override for
+     *  downloading the message.
+     * @param downloadedIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is downloaded, or the download is failed
+     * @throws IllegalArgumentException if locationUrl or contentUri is empty
+     */
+    public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
+            Bundle configOverrides, PendingIntent downloadedIntent) {
+        if (TextUtils.isEmpty(locationUrl)) {
+            throw new IllegalArgumentException("Empty MMS location URL");
+        }
+        if (contentUri == null) {
+            throw new IllegalArgumentException("Uri contentUri null");
+        }
+        try {
+            final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms == null) {
+                return;
+            }
+            iMms.downloadMessage(
+                    getSubscriptionId(), ActivityThread.currentPackageName(), locationUrl,
+                    contentUri, configOverrides, downloadedIntent);
+        } catch (RemoteException e) {
+            // Ignore it
+        }
+    }
+
+    // MMS send/download failure result codes
+    public static final int MMS_ERROR_UNSPECIFIED = 1;
+    public static final int MMS_ERROR_INVALID_APN = 2;
+    public static final int MMS_ERROR_UNABLE_CONNECT_MMS = 3;
+    public static final int MMS_ERROR_HTTP_FAILURE = 4;
+    public static final int MMS_ERROR_IO_ERROR = 5;
+    public static final int MMS_ERROR_RETRY = 6;
+    public static final int MMS_ERROR_CONFIGURATION_ERROR = 7;
+    public static final int MMS_ERROR_NO_DATA_NETWORK = 8;
+
+    /** Intent extra name for MMS sending result data in byte array type */
+    public static final String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
+    /** Intent extra name for HTTP status code for MMS HTTP failure in integer type */
+    public static final String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
+
+    /**
+     * Import a text message into system's SMS store
+     *
+     * Only default SMS apps can import SMS
+     *
+     * @param address the destination(source) address of the sent(received) message
+     * @param type the type of the message
+     * @param text the message text
+     * @param timestampMillis the message timestamp in milliseconds
+     * @param seen if the message is seen
+     * @param read if the message is read
+     * @return the message URI, null if failed
+     * @hide
+     */
+    public Uri importTextMessage(String address, int type, String text, long timestampMillis,
+            boolean seen, boolean read) {
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.importTextMessage(ActivityThread.currentPackageName(),
+                        address, type, text, timestampMillis, seen, read);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return null;
+    }
+
+    /** Represents the received SMS message for importing {@hide} */
+    public static final int SMS_TYPE_INCOMING = 0;
+    /** Represents the sent SMS message for importing {@hide} */
+    public static final int SMS_TYPE_OUTGOING = 1;
+
+    /**
+     * Import a multimedia message into system's MMS store. Only the following PDU type is
+     * supported: Retrieve.conf, Send.req, Notification.ind, Delivery.ind, Read-Orig.ind
+     *
+     * Only default SMS apps can import MMS
+     *
+     * @param contentUri the content uri from which to read the PDU of the message to import
+     * @param messageId the optional message id. Use null if not specifying
+     * @param timestampSecs the optional message timestamp. Use -1 if not specifying
+     * @param seen if the message is seen
+     * @param read if the message is read
+     * @return the message URI, null if failed
+     * @throws IllegalArgumentException if pdu is empty
+     * {@hide}
+     */
+    public Uri importMultimediaMessage(Uri contentUri, String messageId, long timestampSecs,
+            boolean seen, boolean read) {
+        if (contentUri == null) {
+            throw new IllegalArgumentException("Uri contentUri null");
+        }
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.importMultimediaMessage(ActivityThread.currentPackageName(),
+                        contentUri, messageId, timestampSecs, seen, read);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return null;
+    }
+
+    /**
+     * Delete a system stored SMS or MMS message
+     *
+     * Only default SMS apps can delete system stored SMS and MMS messages
+     *
+     * @param messageUri the URI of the stored message
+     * @return true if deletion is successful, false otherwise
+     * @throws IllegalArgumentException if messageUri is empty
+     * {@hide}
+     */
+    public boolean deleteStoredMessage(Uri messageUri) {
+        if (messageUri == null) {
+            throw new IllegalArgumentException("Empty message URI");
+        }
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.deleteStoredMessage(ActivityThread.currentPackageName(), messageUri);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return false;
+    }
+
+    /**
+     * Delete a system stored SMS or MMS thread
+     *
+     * Only default SMS apps can delete system stored SMS and MMS conversations
+     *
+     * @param conversationId the ID of the message conversation
+     * @return true if deletion is successful, false otherwise
+     * {@hide}
+     */
+    public boolean deleteStoredConversation(long conversationId) {
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.deleteStoredConversation(
+                        ActivityThread.currentPackageName(), conversationId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return false;
+    }
+
+    /**
+     * Update the status properties of a system stored SMS or MMS message, e.g.
+     * the read status of a message, etc.
+     *
+     * @param messageUri the URI of the stored message
+     * @param statusValues a list of status properties in key-value pairs to update
+     * @return true if update is successful, false otherwise
+     * @throws IllegalArgumentException if messageUri is empty
+     * {@hide}
+     */
+    public boolean updateStoredMessageStatus(Uri messageUri, ContentValues statusValues) {
+        if (messageUri == null) {
+            throw new IllegalArgumentException("Empty message URI");
+        }
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.updateStoredMessageStatus(ActivityThread.currentPackageName(),
+                        messageUri, statusValues);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return false;
+    }
+
+    /** Message status property: whether the message has been seen. 1 means seen, 0 not {@hide} */
+    public static final String MESSAGE_STATUS_SEEN = "seen";
+    /** Message status property: whether the message has been read. 1 means read, 0 not {@hide} */
+    public static final String MESSAGE_STATUS_READ = "read";
+
+    /**
+     * Archive or unarchive a stored conversation
+     *
+     * @param conversationId the ID of the message conversation
+     * @param archived true to archive the conversation, false to unarchive
+     * @return true if update is successful, false otherwise
+     * {@hide}
+     */
+    public boolean archiveStoredConversation(long conversationId, boolean archived) {
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.archiveStoredConversation(ActivityThread.currentPackageName(),
+                        conversationId, archived);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return false;
+    }
+
+    /**
+     * Add a text message draft to system SMS store
+     *
+     * Only default SMS apps can add SMS draft
+     *
+     * @param address the destination address of message
+     * @param text the body of the message to send
+     * @return the URI of the stored draft message
+     * {@hide}
+     */
+    public Uri addTextMessageDraft(String address, String text) {
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.addTextMessageDraft(ActivityThread.currentPackageName(), address, text);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return null;
+    }
+
+    /**
+     * Add a multimedia message draft to system MMS store
+     *
+     * Only default SMS apps can add MMS draft
+     *
+     * @param contentUri the content uri from which to read the PDU data of the draft MMS
+     * @return the URI of the stored draft message
+     * @throws IllegalArgumentException if pdu is empty
+     * {@hide}
+     */
+    public Uri addMultimediaMessageDraft(Uri contentUri) {
+        if (contentUri == null) {
+            throw new IllegalArgumentException("Uri contentUri null");
+        }
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.addMultimediaMessageDraft(ActivityThread.currentPackageName(),
+                        contentUri);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return null;
+    }
+
+    /**
+     * Send a system stored text message.
+     *
+     * You can only send a failed text message or a draft text message.
+     *
+     * @param messageUri the URI of the stored message
+     * @param scAddress is the service center address or null to use the current default SMSC
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     *
+     * @throws IllegalArgumentException if messageUri is empty
+     * {@hide}
+     */
+    public void sendStoredTextMessage(Uri messageUri, String scAddress, PendingIntent sentIntent,
+            PendingIntent deliveryIntent) {
+        if (messageUri == null) {
+            throw new IllegalArgumentException("Empty message URI");
+        }
+        try {
+            ISms iccISms = getISmsServiceOrThrow();
+            iccISms.sendStoredText(
+                    getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+                    scAddress, sentIntent, deliveryIntent);
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Send a system stored multi-part text message.
+     *
+     * You can only send a failed text message or a draft text message.
+     * The provided <code>PendingIntent</code> lists should match the part number of the
+     * divided text of the stored message by using <code>divideMessage</code>
+     *
+     * @param messageUri the URI of the stored message
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK</code> for success,
+     *   or one of these errors:<br>
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *   <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *   <code>RESULT_ERROR_NULL_PDU</code><br>
+     *   For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+     *   the extra "errorCode" containing a radio technology specific value,
+     *   generally only useful for troubleshooting.<br>
+     *   The per-application based SMS control checks sentIntent. If sentIntent
+     *   is NULL the caller will be checked against all unknown applications,
+     *   which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     *
+     * @throws IllegalArgumentException if messageUri is empty
+     * {@hide}
+     */
+    public void sendStoredMultipartTextMessage(Uri messageUri, String scAddress,
+            ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+        if (messageUri == null) {
+            throw new IllegalArgumentException("Empty message URI");
+        }
+        try {
+            ISms iccISms = getISmsServiceOrThrow();
+            iccISms.sendStoredMultipartText(
+                    getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+                    scAddress, sentIntents, deliveryIntents);
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Send a system stored MMS message
+     *
+     * This is used for sending a previously sent, but failed-to-send, message or
+     * for sending a text message that has been stored as a draft.
+     *
+     * @param messageUri the URI of the stored message
+     * @param configOverrides the carrier-specific messaging configuration values to override for
+     *  sending the message.
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed
+     * @throws IllegalArgumentException if messageUri is empty
+     * {@hide}
+     */
+    public void sendStoredMultimediaMessage(Uri messageUri, Bundle configOverrides,
+            PendingIntent sentIntent) {
+        if (messageUri == null) {
+            throw new IllegalArgumentException("Empty message URI");
+        }
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                iMms.sendStoredMessage(
+                        getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+                        configOverrides, sentIntent);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Turns on/off the flag to automatically write sent/received SMS/MMS messages into system
+     *
+     * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+     * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+     * automatically
+     *
+     * This flag can only be changed by default SMS apps
+     *
+     * @param enabled Whether to enable message auto persisting
+     * {@hide}
+     */
+    public void setAutoPersisting(boolean enabled) {
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                iMms.setAutoPersisting(ActivityThread.currentPackageName(), enabled);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Get the value of the flag to automatically write sent/received SMS/MMS messages into system
+     *
+     * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+     * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+     * automatically
+     *
+     * @return the current value of the auto persist flag
+     * {@hide}
+     */
+    public boolean getAutoPersisting() {
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.getAutoPersisting();
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return false;
+    }
+
+    /**
+     * Get carrier-dependent configuration values.
+     *
+     * @return bundle key/values pairs of configuration values
+     */
+    public Bundle getCarrierConfigValues() {
+        try {
+            IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+            if (iMms != null) {
+                return iMms.getCarrierConfigValues(getSubscriptionId());
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+        return null;
+    }
+
+    /**
+     * Filters a bundle to only contain MMS config variables.
+     *
+     * This is for use with bundles returned by {@link CarrierConfigManager} which contain MMS
+     * config and unrelated config. It is assumed that all MMS_CONFIG_* keys are present in the
+     * supplied bundle.
+     *
+     * @param config a Bundle that contains MMS config variables and possibly more.
+     * @return a new Bundle that only contains the MMS_CONFIG_* keys defined above.
+     * @hide
+     */
+    public static Bundle getMmsConfig(BaseBundle config) {
+        Bundle filtered = new Bundle();
+        filtered.putBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID,
+                config.getBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID));
+        filtered.putBoolean(MMS_CONFIG_MMS_ENABLED, config.getBoolean(MMS_CONFIG_MMS_ENABLED));
+        filtered.putBoolean(MMS_CONFIG_GROUP_MMS_ENABLED,
+                config.getBoolean(MMS_CONFIG_GROUP_MMS_ENABLED));
+        filtered.putBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED,
+                config.getBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED));
+        filtered.putBoolean(MMS_CONFIG_ALIAS_ENABLED, config.getBoolean(MMS_CONFIG_ALIAS_ENABLED));
+        filtered.putBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO,
+                config.getBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO));
+        filtered.putBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED,
+                config.getBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED));
+        filtered.putBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED,
+                config.getBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED));
+        filtered.putBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
+                config.getBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION));
+        filtered.putBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
+                config.getBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES));
+        filtered.putBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED,
+                config.getBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED));
+        filtered.putBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED,
+                config.getBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED));
+        filtered.putBoolean(MMS_CONFIG_CLOSE_CONNECTION,
+                config.getBoolean(MMS_CONFIG_CLOSE_CONNECTION));
+        filtered.putInt(MMS_CONFIG_MAX_MESSAGE_SIZE, config.getInt(MMS_CONFIG_MAX_MESSAGE_SIZE));
+        filtered.putInt(MMS_CONFIG_MAX_IMAGE_WIDTH, config.getInt(MMS_CONFIG_MAX_IMAGE_WIDTH));
+        filtered.putInt(MMS_CONFIG_MAX_IMAGE_HEIGHT, config.getInt(MMS_CONFIG_MAX_IMAGE_HEIGHT));
+        filtered.putInt(MMS_CONFIG_RECIPIENT_LIMIT, config.getInt(MMS_CONFIG_RECIPIENT_LIMIT));
+        filtered.putInt(MMS_CONFIG_ALIAS_MIN_CHARS, config.getInt(MMS_CONFIG_ALIAS_MIN_CHARS));
+        filtered.putInt(MMS_CONFIG_ALIAS_MAX_CHARS, config.getInt(MMS_CONFIG_ALIAS_MAX_CHARS));
+        filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD,
+                config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD));
+        filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
+                config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD));
+        filtered.putInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE,
+                config.getInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE));
+        filtered.putInt(MMS_CONFIG_SUBJECT_MAX_LENGTH,
+                config.getInt(MMS_CONFIG_SUBJECT_MAX_LENGTH));
+        filtered.putInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT,
+                config.getInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT));
+        filtered.putString(MMS_CONFIG_UA_PROF_TAG_NAME,
+                config.getString(MMS_CONFIG_UA_PROF_TAG_NAME));
+        filtered.putString(MMS_CONFIG_USER_AGENT, config.getString(MMS_CONFIG_USER_AGENT));
+        filtered.putString(MMS_CONFIG_UA_PROF_URL, config.getString(MMS_CONFIG_UA_PROF_URL));
+        filtered.putString(MMS_CONFIG_HTTP_PARAMS, config.getString(MMS_CONFIG_HTTP_PARAMS));
+        filtered.putString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER,
+                config.getString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER));
+        filtered.putString(MMS_CONFIG_NAI_SUFFIX, config.getString(MMS_CONFIG_NAI_SUFFIX));
+        filtered.putBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS,
+                config.getBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS));
+        filtered.putBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
+                config.getBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER));
+        return filtered;
+    }
+
+}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
new file mode 100644
index 0000000..dcdda86
--- /dev/null
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -0,0 +1,926 @@
+/*
+ * 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.telephony;
+
+import android.os.Binder;
+import android.os.Parcel;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.lang.Math;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+
+/**
+ * A Short Message Service message.
+ * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
+ */
+public class SmsMessage {
+    private static final String LOG_TAG = "SmsMessage";
+
+    /**
+     * SMS Class enumeration.
+     * See TS 23.038.
+     *
+     */
+    public enum MessageClass{
+        UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
+    }
+
+    /** User data text encoding code unit size */
+    public static final int ENCODING_UNKNOWN = 0;
+    public static final int ENCODING_7BIT = 1;
+    public static final int ENCODING_8BIT = 2;
+    public static final int ENCODING_16BIT = 3;
+    /**
+     * @hide This value is not defined in global standard. Only in Korea, this is used.
+     */
+    public static final int ENCODING_KSC5601 = 4;
+
+    /** The maximum number of payload bytes per message */
+    public static final int MAX_USER_DATA_BYTES = 140;
+
+    /**
+     * The maximum number of payload bytes per message if a user data header
+     * is present.  This assumes the header only contains the
+     * CONCATENATED_8_BIT_REFERENCE element.
+     */
+    public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134;
+
+    /** The maximum number of payload septets per message */
+    public static final int MAX_USER_DATA_SEPTETS = 160;
+
+    /**
+     * The maximum number of payload septets per message if a user data header
+     * is present.  This assumes the header only contains the
+     * CONCATENATED_8_BIT_REFERENCE element.
+     */
+    public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
+
+    /**
+     * Indicates a 3GPP format SMS message.
+     * @hide pending API council approval
+     */
+    public static final String FORMAT_3GPP = "3gpp";
+
+    /**
+     * Indicates a 3GPP2 format SMS message.
+     * @hide pending API council approval
+     */
+    public static final String FORMAT_3GPP2 = "3gpp2";
+
+    /** Contains actual SmsMessage. Only public for debugging and for framework layer.
+     *
+     * @hide
+     */
+    public SmsMessageBase mWrappedSmsMessage;
+
+    /** Indicates the subId
+     *
+     * @hide
+     */
+    private int mSubId = 0;
+
+    /** set Subscription information
+     *
+     * @hide
+     */
+    public void setSubId(int subId) {
+        mSubId = subId;
+    }
+
+    /** get Subscription information
+     *
+     * @hide
+     */
+    public int getSubId() {
+        return mSubId;
+    }
+
+    public static class SubmitPdu {
+
+        public byte[] encodedScAddress; // Null if not applicable.
+        public byte[] encodedMessage;
+
+        @Override
+        public String toString() {
+            return "SubmitPdu: encodedScAddress = "
+                    + Arrays.toString(encodedScAddress)
+                    + ", encodedMessage = "
+                    + Arrays.toString(encodedMessage);
+        }
+
+        /**
+         * @hide
+         */
+        protected SubmitPdu(SubmitPduBase spb) {
+            this.encodedMessage = spb.encodedMessage;
+            this.encodedScAddress = spb.encodedScAddress;
+        }
+
+    }
+
+    /**
+     * @hide
+     */
+    public SmsMessage(SmsMessageBase smb) {
+        mWrappedSmsMessage = smb;
+    }
+
+    /**
+     * Create an SmsMessage from a raw PDU. Guess format based on Voice
+     * technology first, if it fails use other format.
+     * All applications which handle
+     * incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast
+     * intent <b>must</b> now pass the new {@code format} String extra from the intent
+     * into the new method {@code createFromPdu(byte[], String)} which takes an
+     * extra format parameter. This is required in order to correctly decode the PDU on
+     * devices that require support for both 3GPP and 3GPP2 formats at the same time,
+     * such as dual-mode GSM/CDMA and CDMA/LTE phones.
+     * @deprecated Use {@link #createFromPdu(byte[], String)} instead.
+     */
+    @Deprecated
+    public static SmsMessage createFromPdu(byte[] pdu) {
+         SmsMessage message = null;
+
+        // cdma(3gpp2) vs gsm(3gpp) format info was not given,
+        // guess from active voice phone type
+        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+        String format = (PHONE_TYPE_CDMA == activePhone) ?
+                SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
+        message = createFromPdu(pdu, format);
+
+        if (null == message || null == message.mWrappedSmsMessage) {
+            // decoding pdu failed based on activePhone type, must be other format
+            format = (PHONE_TYPE_CDMA == activePhone) ?
+                    SmsConstants.FORMAT_3GPP : SmsConstants.FORMAT_3GPP2;
+            message = createFromPdu(pdu, format);
+        }
+        return message;
+    }
+
+    /**
+     * Create an SmsMessage from a raw PDU with the specified message format. The
+     * message format is passed in the
+     * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} as the {@code format}
+     * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
+     * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
+     *
+     * @param pdu the message PDU from the
+     * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
+     * @param format the format extra from the
+     * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
+     */
+    public static SmsMessage createFromPdu(byte[] pdu, String format) {
+        SmsMessageBase wrappedMessage;
+
+        if (SmsConstants.FORMAT_3GPP2.equals(format)) {
+            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
+        } else if (SmsConstants.FORMAT_3GPP.equals(format)) {
+            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
+        } else {
+            Rlog.e(LOG_TAG, "createFromPdu(): unsupported message format " + format);
+            return null;
+        }
+
+        if (wrappedMessage != null) {
+            return new SmsMessage(wrappedMessage);
+        } else {
+            Rlog.e(LOG_TAG, "createFromPdu(): wrappedMessage is null");
+            return null;
+        }
+    }
+
+    /**
+     * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+     * +CMT unsolicited response (PDU mode, of course)
+     *  +CMT: [&lt;alpha>],<length><CR><LF><pdu>
+     *
+     * Only public for debugging and for RIL
+     *
+     * {@hide}
+     */
+    public static SmsMessage newFromCMT(byte[] pdu) {
+        // received SMS in 3GPP format
+        SmsMessageBase wrappedMessage =
+                com.android.internal.telephony.gsm.SmsMessage.newFromCMT(pdu);
+
+        if (wrappedMessage != null) {
+            return new SmsMessage(wrappedMessage);
+        } else {
+            Rlog.e(LOG_TAG, "newFromCMT(): wrappedMessage is null");
+            return null;
+        }
+    }
+
+    /**
+     * Create an SmsMessage from an SMS EF record.
+     *
+     * @param index Index of SMS record. This should be index in ArrayList
+     *              returned by SmsManager.getAllMessagesFromSim + 1.
+     * @param data Record data.
+     * @return An SmsMessage representing the record.
+     *
+     * @hide
+     */
+    public static SmsMessage createFromEfRecord(int index, byte[] data) {
+        SmsMessageBase wrappedMessage;
+
+        if (isCdmaVoice()) {
+            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+                    index, data);
+        } else {
+            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+                    index, data);
+        }
+
+        if (wrappedMessage != null) {
+            return new SmsMessage(wrappedMessage);
+        } else {
+            Rlog.e(LOG_TAG, "createFromEfRecord(): wrappedMessage is null");
+            return null;
+        }
+    }
+
+    /**
+     * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
+     * length in bytes (not hex chars) less the SMSC header
+     *
+     * FIXME: This method is only used by a CTS test case that isn't run on CDMA devices.
+     * We should probably deprecate it and remove the obsolete test case.
+     */
+    public static int getTPLayerLengthForPDU(String pdu) {
+        if (isCdmaVoice()) {
+            return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu);
+        } else {
+            return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu);
+        }
+    }
+
+    /*
+     * TODO(cleanup): It would make some sense if the result of
+     * preprocessing a message to determine the proper encoding (i.e.
+     * the resulting data structure from calculateLength) could be
+     * passed as an argument to the actual final encoding function.
+     * This would better ensure that the logic behind size calculation
+     * actually matched the encoding.
+     */
+
+    /**
+     * Calculates the number of SMS's required to encode the message body and
+     * the number of characters remaining until the next message.
+     *
+     * @param msgBody the message to encode
+     * @param use7bitOnly if true, characters that are not part of the
+     *         radio-specific 7-bit encoding are counted as single
+     *         space chars.  If false, and if the messageBody contains
+     *         non-7-bit encodable characters, length is calculated
+     *         using a 16-bit encoding.
+     * @return an int[4] with int[0] being the number of SMS's
+     *         required, int[1] the number of code units used, and
+     *         int[2] is the number of code units remaining until the
+     *         next message. int[3] is an indicator of the encoding
+     *         code unit size (see the ENCODING_* definitions in SmsConstants)
+     */
+    public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) {
+        // this function is for MO SMS
+        TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
+            com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly,
+                    true) :
+            com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly);
+        int ret[] = new int[4];
+        ret[0] = ted.msgCount;
+        ret[1] = ted.codeUnitCount;
+        ret[2] = ted.codeUnitsRemaining;
+        ret[3] = ted.codeUnitSize;
+        return ret;
+    }
+
+    /**
+     * Divide a message text into several fragments, none bigger than
+     * the maximum SMS message text size.
+     *
+     * @param text text, must not be null.
+     * @return an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original msg text
+     *
+     * @hide
+     */
+    public static ArrayList<String> fragmentText(String text) {
+        // This function is for MO SMS
+        TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
+            com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false, true) :
+            com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false);
+
+        // TODO(cleanup): The code here could be rolled into the logic
+        // below cleanly if these MAX_* constants were defined more
+        // flexibly...
+
+        int limit;
+        if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
+            int udhLength;
+            if (ted.languageTable != 0 && ted.languageShiftTable != 0) {
+                udhLength = GsmAlphabet.UDH_SEPTET_COST_TWO_SHIFT_TABLES;
+            } else if (ted.languageTable != 0 || ted.languageShiftTable != 0) {
+                udhLength = GsmAlphabet.UDH_SEPTET_COST_ONE_SHIFT_TABLE;
+            } else {
+                udhLength = 0;
+            }
+
+            if (ted.msgCount > 1) {
+                udhLength += GsmAlphabet.UDH_SEPTET_COST_CONCATENATED_MESSAGE;
+            }
+
+            if (udhLength != 0) {
+                udhLength += GsmAlphabet.UDH_SEPTET_COST_LENGTH;
+            }
+
+            limit = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength;
+        } else {
+            if (ted.msgCount > 1) {
+                limit = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+                // If EMS is not supported, break down EMS into single segment SMS
+                // and add page info " x/y".
+                // In the case of UCS2 encoding, we need 8 bytes for this,
+                // but we only have 6 bytes from UDH, so truncate the limit for
+                // each segment by 2 bytes (1 char).
+                // Make sure total number of segments is less than 10.
+                if (!hasEmsSupport() && ted.msgCount < 10) {
+                    limit -= 2;
+                }
+            } else {
+                limit = SmsConstants.MAX_USER_DATA_BYTES;
+            }
+        }
+
+        String newMsgBody = null;
+        Resources r = Resources.getSystem();
+        if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+            newMsgBody  = Sms7BitEncodingTranslator.translate(text);
+        }
+        if (TextUtils.isEmpty(newMsgBody)) {
+            newMsgBody = text;
+        }
+        int pos = 0;  // Index in code units.
+        int textLen = newMsgBody.length();
+        ArrayList<String> result = new ArrayList<String>(ted.msgCount);
+        while (pos < textLen) {
+            int nextPos = 0;  // Counts code units.
+            if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
+                if (useCdmaFormatForMoSms() && ted.msgCount == 1) {
+                    // For a singleton CDMA message, the encoding must be ASCII...
+                    nextPos = pos + Math.min(limit, textLen - pos);
+                } else {
+                    // For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode).
+                    nextPos = GsmAlphabet.findGsmSeptetLimitIndex(newMsgBody, pos, limit,
+                            ted.languageTable, ted.languageShiftTable);
+                }
+            } else {  // Assume unicode.
+                nextPos = SmsMessageBase.findNextUnicodePosition(pos, limit, newMsgBody);
+            }
+            if ((nextPos <= pos) || (nextPos > textLen)) {
+                Rlog.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " +
+                          nextPos + " >= " + textLen + ")");
+                break;
+            }
+            result.add(newMsgBody.substring(pos, nextPos));
+            pos = nextPos;
+        }
+        return result;
+    }
+
+    /**
+     * Calculates the number of SMS's required to encode the message body and
+     * the number of characters remaining until the next message, given the
+     * current encoding.
+     *
+     * @param messageBody the message to encode
+     * @param use7bitOnly if true, characters that are not part of the radio
+     *         specific (GSM / CDMA) alphabet encoding are converted to as a
+     *         single space characters. If false, a messageBody containing
+     *         non-GSM or non-CDMA alphabet characters are encoded using
+     *         16-bit encoding.
+     * @return an int[4] with int[0] being the number of SMS's required, int[1]
+     *         the number of code units used, and int[2] is the number of code
+     *         units remaining until the next message. int[3] is the encoding
+     *         type that should be used for the message.
+     */
+    public static int[] calculateLength(String messageBody, boolean use7bitOnly) {
+        return calculateLength((CharSequence)messageBody, use7bitOnly);
+    }
+
+    /*
+     * TODO(cleanup): It looks like there is now no useful reason why
+     * apps should generate pdus themselves using these routines,
+     * instead of handing the raw data to SMSDispatcher (and thereby
+     * have the phone process do the encoding).  Moreover, CDMA now
+     * has shared state (in the form of the msgId system property)
+     * which can only be modified by the phone process, and hence
+     * makes the output of these routines incorrect.  Since they now
+     * serve no purpose, they should probably just return null
+     * directly, and be deprecated.  Going further in that direction,
+     * the above parsers of serialized pdu data should probably also
+     * be gotten rid of, hiding all but the necessarily visible
+     * structured data from client apps.  A possible concern with
+     * doing this is that apps may be using these routines to generate
+     * pdus that are then sent elsewhere, some network server, for
+     * example, and that always returning null would thereby break
+     * otherwise useful apps.
+     */
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message.
+     * This method will not attempt to use any GSM national language 7 bit encodings.
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message, boolean statusReportRequested) {
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+                SubscriptionManager.getDefaultSmsSubscriptionId());
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message.
+     * This method will not attempt to use any GSM national language 7 bit encodings.
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @param destinationAddress the address of the destination for the message.
+     * @param message String representation of the message payload.
+     * @param statusReportRequested Indicates whether a report is requested for this message.
+     * @param subId Subscription of the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message, boolean statusReportRequested, int subId) {
+        SubmitPduBase spb;
+        if (useCdmaFormatForMoSms(subId)) {
+            spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+                    destinationAddress, message, statusReportRequested, null);
+        } else {
+            spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+                    destinationAddress, message, statusReportRequested);
+        }
+
+        return new SubmitPdu(spb);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
+     * This method will not attempt to use any GSM national language 7 bit encodings.
+     *
+     * @param scAddress Service Centre address. null == use default
+     * @param destinationAddress the address of the destination for the message
+     * @param destinationPort the port to deliver the message to at the
+     *        destination
+     * @param data the data for the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, short destinationPort, byte[] data,
+            boolean statusReportRequested) {
+        SubmitPduBase spb;
+
+        if (useCdmaFormatForMoSms()) {
+            spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+                    destinationAddress, destinationPort, data, statusReportRequested);
+        } else {
+            spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+                    destinationAddress, destinationPort, data, statusReportRequested);
+        }
+
+        return new SubmitPdu(spb);
+    }
+
+    /**
+     * Returns the address of the SMS service center that relayed this message
+     * or null if there is none.
+     */
+    public String getServiceCenterAddress() {
+        return mWrappedSmsMessage.getServiceCenterAddress();
+    }
+
+    /**
+     * Returns the originating address (sender) of this SMS message in String
+     * form or null if unavailable
+     */
+    public String getOriginatingAddress() {
+        return mWrappedSmsMessage.getOriginatingAddress();
+    }
+
+    /**
+     * Returns the originating address, or email from address if this message
+     * was from an email gateway. Returns null if originating address
+     * unavailable.
+     */
+    public String getDisplayOriginatingAddress() {
+        return mWrappedSmsMessage.getDisplayOriginatingAddress();
+    }
+
+    /**
+     * Returns the message body as a String, if it exists and is text based.
+     * @return message body is there is one, otherwise null
+     */
+    public String getMessageBody() {
+        return mWrappedSmsMessage.getMessageBody();
+    }
+
+    /**
+     * Returns the class of this message.
+     */
+    public MessageClass getMessageClass() {
+        switch(mWrappedSmsMessage.getMessageClass()) {
+            case CLASS_0: return MessageClass.CLASS_0;
+            case CLASS_1: return MessageClass.CLASS_1;
+            case CLASS_2: return MessageClass.CLASS_2;
+            case CLASS_3: return MessageClass.CLASS_3;
+            default: return MessageClass.UNKNOWN;
+
+        }
+    }
+
+    /**
+     * Returns the message body, or email message body if this message was from
+     * an email gateway. Returns null if message body unavailable.
+     */
+    public String getDisplayMessageBody() {
+        return mWrappedSmsMessage.getDisplayMessageBody();
+    }
+
+    /**
+     * Unofficial convention of a subject line enclosed in parens empty string
+     * if not present
+     */
+    public String getPseudoSubject() {
+        return mWrappedSmsMessage.getPseudoSubject();
+    }
+
+    /**
+     * Returns the service centre timestamp in currentTimeMillis() format
+     */
+    public long getTimestampMillis() {
+        return mWrappedSmsMessage.getTimestampMillis();
+    }
+
+    /**
+     * Returns true if message is an email.
+     *
+     * @return true if this message came through an email gateway and email
+     *         sender / subject / parsed body are available
+     */
+    public boolean isEmail() {
+        return mWrappedSmsMessage.isEmail();
+    }
+
+     /**
+     * @return if isEmail() is true, body of the email sent through the gateway.
+     *         null otherwise
+     */
+    public String getEmailBody() {
+        return mWrappedSmsMessage.getEmailBody();
+    }
+
+    /**
+     * @return if isEmail() is true, email from address of email sent through
+     *         the gateway. null otherwise
+     */
+    public String getEmailFrom() {
+        return mWrappedSmsMessage.getEmailFrom();
+    }
+
+    /**
+     * Get protocol identifier.
+     */
+    public int getProtocolIdentifier() {
+        return mWrappedSmsMessage.getProtocolIdentifier();
+    }
+
+    /**
+     * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+     * SMS
+     */
+    public boolean isReplace() {
+        return mWrappedSmsMessage.isReplace();
+    }
+
+    /**
+     * Returns true for CPHS MWI toggle message.
+     *
+     * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
+     *         B.4.2
+     */
+    public boolean isCphsMwiMessage() {
+        return mWrappedSmsMessage.isCphsMwiMessage();
+    }
+
+    /**
+     * returns true if this message is a CPHS voicemail / message waiting
+     * indicator (MWI) clear message
+     */
+    public boolean isMWIClearMessage() {
+        return mWrappedSmsMessage.isMWIClearMessage();
+    }
+
+    /**
+     * returns true if this message is a CPHS voicemail / message waiting
+     * indicator (MWI) set message
+     */
+    public boolean isMWISetMessage() {
+        return mWrappedSmsMessage.isMWISetMessage();
+    }
+
+    /**
+     * returns true if this message is a "Message Waiting Indication Group:
+     * Discard Message" notification and should not be stored.
+     */
+    public boolean isMwiDontStore() {
+        return mWrappedSmsMessage.isMwiDontStore();
+    }
+
+    /**
+     * returns the user data section minus the user data header if one was
+     * present.
+     */
+    public byte[] getUserData() {
+        return mWrappedSmsMessage.getUserData();
+    }
+
+    /**
+     * Returns the raw PDU for the message.
+     *
+     * @return the raw PDU for the message.
+     */
+    public byte[] getPdu() {
+        return mWrappedSmsMessage.getPdu();
+    }
+
+    /**
+     * Returns the status of the message on the SIM (read, unread, sent, unsent).
+     *
+     * @return the status of the message on the SIM.  These are:
+     *         SmsManager.STATUS_ON_SIM_FREE
+     *         SmsManager.STATUS_ON_SIM_READ
+     *         SmsManager.STATUS_ON_SIM_UNREAD
+     *         SmsManager.STATUS_ON_SIM_SEND
+     *         SmsManager.STATUS_ON_SIM_UNSENT
+     * @deprecated Use getStatusOnIcc instead.
+     */
+    @Deprecated public int getStatusOnSim() {
+        return mWrappedSmsMessage.getStatusOnIcc();
+    }
+
+    /**
+     * Returns the status of the message on the ICC (read, unread, sent, unsent).
+     *
+     * @return the status of the message on the ICC.  These are:
+     *         SmsManager.STATUS_ON_ICC_FREE
+     *         SmsManager.STATUS_ON_ICC_READ
+     *         SmsManager.STATUS_ON_ICC_UNREAD
+     *         SmsManager.STATUS_ON_ICC_SEND
+     *         SmsManager.STATUS_ON_ICC_UNSENT
+     */
+    public int getStatusOnIcc() {
+        return mWrappedSmsMessage.getStatusOnIcc();
+    }
+
+    /**
+     * Returns the record index of the message on the SIM (1-based index).
+     * @return the record index of the message on the SIM, or -1 if this
+     *         SmsMessage was not created from a SIM SMS EF record.
+     * @deprecated Use getIndexOnIcc instead.
+     */
+    @Deprecated public int getIndexOnSim() {
+        return mWrappedSmsMessage.getIndexOnIcc();
+    }
+
+    /**
+     * Returns the record index of the message on the ICC (1-based index).
+     * @return the record index of the message on the ICC, or -1 if this
+     *         SmsMessage was not created from a ICC SMS EF record.
+     */
+    public int getIndexOnIcc() {
+        return mWrappedSmsMessage.getIndexOnIcc();
+    }
+
+    /**
+     * GSM:
+     * For an SMS-STATUS-REPORT message, this returns the status field from
+     * the status report.  This field indicates the status of a previously
+     * submitted SMS, if requested.  See TS 23.040, 9.2.3.15 TP-Status for a
+     * description of values.
+     * CDMA:
+     * For not interfering with status codes from GSM, the value is
+     * shifted to the bits 31-16.
+     * The value is composed of an error class (bits 25-24) and a status code (bits 23-16).
+     * Possible codes are described in C.S0015-B, v2.0, 4.5.21.
+     *
+     * @return 0 indicates the previously sent message was received.
+     *         See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21
+     *         for a description of other possible values.
+     */
+    public int getStatus() {
+        return mWrappedSmsMessage.getStatus();
+    }
+
+    /**
+     * Return true iff the message is a SMS-STATUS-REPORT message.
+     */
+    public boolean isStatusReportMessage() {
+        return mWrappedSmsMessage.isStatusReportMessage();
+    }
+
+    /**
+     * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+     * this message.
+     */
+    public boolean isReplyPathPresent() {
+        return mWrappedSmsMessage.isReplyPathPresent();
+    }
+
+    /**
+     * Determines whether or not to use CDMA format for MO SMS.
+     * If SMS over IMS is supported, then format is based on IMS SMS format,
+     * otherwise format is based on current phone type.
+     *
+     * @return true if Cdma format should be used for MO SMS, false otherwise.
+     */
+    private static boolean useCdmaFormatForMoSms() {
+        // IMS is registered with SMS support, check the SMS format supported
+        return useCdmaFormatForMoSms(SubscriptionManager.getDefaultSmsSubscriptionId());
+    }
+
+    /**
+     * Determines whether or not to use CDMA format for MO SMS.
+     * If SMS over IMS is supported, then format is based on IMS SMS format,
+     * otherwise format is based on current phone type.
+     *
+     * @param subId Subscription for which phone type is returned.
+     *
+     * @return true if Cdma format should be used for MO SMS, false otherwise.
+     */
+    private static boolean useCdmaFormatForMoSms(int subId) {
+        SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
+        if (!smsManager.isImsSmsSupported()) {
+            // use Voice technology to determine SMS format.
+            return isCdmaVoice(subId);
+        }
+        // IMS is registered with SMS support, check the SMS format supported
+        return (SmsConstants.FORMAT_3GPP2.equals(smsManager.getImsSmsFormat()));
+    }
+
+    /**
+     * Determines whether or not to current phone type is cdma.
+     *
+     * @return true if current phone type is cdma, false otherwise.
+     */
+    private static boolean isCdmaVoice() {
+        return isCdmaVoice(SubscriptionManager.getDefaultSmsSubscriptionId());
+    }
+
+     /**
+      * Determines whether or not to current phone type is cdma
+      *
+      * @return true if current phone type is cdma, false otherwise.
+      */
+     private static boolean isCdmaVoice(int subId) {
+         int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId);
+         return (PHONE_TYPE_CDMA == activePhone);
+   }
+    /**
+     * Decide if the carrier supports long SMS.
+     * {@hide}
+     */
+    public static boolean hasEmsSupport() {
+        if (!isNoEmsSupportConfigListExisted()) {
+            return true;
+        }
+
+        String simOperator;
+        String gid;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
+            gid = TelephonyManager.getDefault().getGroupIdLevel1();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        if (!TextUtils.isEmpty(simOperator)) {
+            for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+                if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
+                        (TextUtils.isEmpty(currentConfig.mGid1) ||
+                                (!TextUtils.isEmpty(currentConfig.mGid1) &&
+                                        currentConfig.mGid1.equalsIgnoreCase(gid)))) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check where to add " x/y" in each SMS segment, begin or end.
+     * {@hide}
+     */
+    public static boolean shouldAppendPageNumberAsPrefix() {
+        if (!isNoEmsSupportConfigListExisted()) {
+            return false;
+        }
+
+        String simOperator;
+        String gid;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
+            gid = TelephonyManager.getDefault().getGroupIdLevel1();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+            if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
+                (TextUtils.isEmpty(currentConfig.mGid1) ||
+                (!TextUtils.isEmpty(currentConfig.mGid1)
+                && currentConfig.mGid1.equalsIgnoreCase(gid)))) {
+                return currentConfig.mIsPrefix;
+            }
+        }
+        return false;
+    }
+
+    private static class NoEmsSupportConfig {
+        String mOperatorNumber;
+        String mGid1;
+        boolean mIsPrefix;
+
+        public NoEmsSupportConfig(String[] config) {
+            mOperatorNumber = config[0];
+            mIsPrefix = "prefix".equals(config[1]);
+            mGid1 = config.length > 2 ? config[2] : null;
+        }
+
+        @Override
+        public String toString() {
+            return "NoEmsSupportConfig { mOperatorNumber = " + mOperatorNumber
+                    + ", mIsPrefix = " + mIsPrefix + ", mGid1 = " + mGid1 + " }";
+        }
+    }
+
+    private static NoEmsSupportConfig[] mNoEmsSupportConfigList = null;
+    private static boolean mIsNoEmsSupportConfigListLoaded = false;
+
+    private static boolean isNoEmsSupportConfigListExisted() {
+        if (!mIsNoEmsSupportConfigListLoaded) {
+            Resources r = Resources.getSystem();
+            if (r != null) {
+                String[] listArray = r.getStringArray(
+                        com.android.internal.R.array.no_ems_support_sim_operators);
+                if ((listArray != null) && (listArray.length > 0)) {
+                    mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
+                    for (int i=0; i<listArray.length; i++) {
+                        mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(listArray[i].split(";"));
+                    }
+                }
+                mIsNoEmsSupportConfigListLoaded = true;
+            }
+        }
+
+        if (mNoEmsSupportConfigList != null && mNoEmsSupportConfigList.length != 0) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/telephony/java/android/telephony/Telephony.java b/telephony/java/android/telephony/Telephony.java
new file mode 100644
index 0000000..a91e05e
--- /dev/null
+++ b/telephony/java/android/telephony/Telephony.java
@@ -0,0 +1,3246 @@
+/*
+ * 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.provider;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.sqlite.SqliteWrapper;
+import android.net.Uri;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Patterns;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SmsApplication;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The Telephony provider contains data related to phone operation, specifically SMS and MMS
+ * messages, access to the APN list, including the MMSC to use, and the service state.
+ *
+ * <p class="note"><strong>Note:</strong> These APIs are not available on all Android-powered
+ * devices. If your app depends on telephony features such as for managing SMS messages, include
+ * a <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}
+ * </a> element in your manifest that declares the {@code "android.hardware.telephony"} hardware
+ * feature. Alternatively, you can check for telephony availability at runtime using either
+ * {@link android.content.pm.PackageManager#hasSystemFeature
+ * hasSystemFeature(PackageManager.FEATURE_TELEPHONY)} or {@link
+ * android.telephony.TelephonyManager#getPhoneType}.</p>
+ *
+ * <h3>Creating an SMS app</h3>
+ *
+ * <p>Only the default SMS app (selected by the user in system settings) is able to write to the
+ * SMS Provider (the tables defined within the {@code Telephony} class) and only the default SMS
+ * app receives the {@link android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION} broadcast
+ * when the user receives an SMS or the {@link
+ * android.provider.Telephony.Sms.Intents#WAP_PUSH_DELIVER_ACTION} broadcast when the user
+ * receives an MMS.</p>
+ *
+ * <p>Any app that wants to behave as the user's default SMS app must handle the following intents:
+ * <ul>
+ * <li>In a broadcast receiver, include an intent filter for {@link Sms.Intents#SMS_DELIVER_ACTION}
+ * (<code>"android.provider.Telephony.SMS_DELIVER"</code>). The broadcast receiver must also
+ * require the {@link android.Manifest.permission#BROADCAST_SMS} permission.
+ * <p>This allows your app to directly receive incoming SMS messages.</p></li>
+ * <li>In a broadcast receiver, include an intent filter for {@link
+ * Sms.Intents#WAP_PUSH_DELIVER_ACTION}} ({@code "android.provider.Telephony.WAP_PUSH_DELIVER"})
+ * with the MIME type <code>"application/vnd.wap.mms-message"</code>.
+ * The broadcast receiver must also require the {@link
+ * android.Manifest.permission#BROADCAST_WAP_PUSH} permission.
+ * <p>This allows your app to directly receive incoming MMS messages.</p></li>
+ * <li>In your activity that delivers new messages, include an intent filter for
+ * {@link android.content.Intent#ACTION_SENDTO} (<code>"android.intent.action.SENDTO"
+ * </code>) with schemas, <code>sms:</code>, <code>smsto:</code>, <code>mms:</code>, and
+ * <code>mmsto:</code>.
+ * <p>This allows your app to receive intents from other apps that want to deliver a
+ * message.</p></li>
+ * <li>In a service, include an intent filter for {@link
+ * android.telephony.TelephonyManager#ACTION_RESPOND_VIA_MESSAGE}
+ * (<code>"android.intent.action.RESPOND_VIA_MESSAGE"</code>) with schemas,
+ * <code>sms:</code>, <code>smsto:</code>, <code>mms:</code>, and <code>mmsto:</code>.
+ * This service must also require the {@link
+ * android.Manifest.permission#SEND_RESPOND_VIA_MESSAGE} permission.
+ * <p>This allows users to respond to incoming phone calls with an immediate text message
+ * using your app.</p></li>
+ * </ul>
+ *
+ * <p>Other apps that are not selected as the default SMS app can only <em>read</em> the SMS
+ * Provider, but may also be notified when a new SMS arrives by listening for the {@link
+ * Sms.Intents#SMS_RECEIVED_ACTION}
+ * broadcast, which is a non-abortable broadcast that may be delivered to multiple apps. This
+ * broadcast is intended for apps that&mdash;while not selected as the default SMS app&mdash;need to
+ * read special incoming messages such as to perform phone number verification.</p>
+ *
+ * <p>For more information about building SMS apps, read the blog post, <a
+ * href="http://android-developers.blogspot.com/2013/10/getting-your-sms-apps-ready-for-kitkat.html"
+ * >Getting Your SMS Apps Ready for KitKat</a>.</p>
+ *
+ */
+public final class Telephony {
+    private static final String TAG = "Telephony";
+
+    /**
+     * Not instantiable.
+     * @hide
+     */
+    private Telephony() {
+    }
+
+    /**
+     * Base columns for tables that contain text-based SMSs.
+     */
+    public interface TextBasedSmsColumns {
+
+        /** Message type: all messages. */
+        public static final int MESSAGE_TYPE_ALL    = 0;
+
+        /** Message type: inbox. */
+        public static final int MESSAGE_TYPE_INBOX  = 1;
+
+        /** Message type: sent messages. */
+        public static final int MESSAGE_TYPE_SENT   = 2;
+
+        /** Message type: drafts. */
+        public static final int MESSAGE_TYPE_DRAFT  = 3;
+
+        /** Message type: outbox. */
+        public static final int MESSAGE_TYPE_OUTBOX = 4;
+
+        /** Message type: failed outgoing message. */
+        public static final int MESSAGE_TYPE_FAILED = 5;
+
+        /** Message type: queued to send later. */
+        public static final int MESSAGE_TYPE_QUEUED = 6;
+
+        /**
+         * The type of message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String TYPE = "type";
+
+        /**
+         * The thread ID of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String THREAD_ID = "thread_id";
+
+        /**
+         * The address of the other party.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ADDRESS = "address";
+
+        /**
+         * The date the message was received.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * The date the message was sent.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE_SENT = "date_sent";
+
+        /**
+         * Has the message been read?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String READ = "read";
+
+        /**
+         * Has the message been seen by the user? The "seen" flag determines
+         * whether we need to show a notification.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String SEEN = "seen";
+
+        /**
+         * {@code TP-Status} value for the message, or -1 if no status has been received.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String STATUS = "status";
+
+        /** TP-Status: no status received. */
+        public static final int STATUS_NONE = -1;
+        /** TP-Status: complete. */
+        public static final int STATUS_COMPLETE = 0;
+        /** TP-Status: pending. */
+        public static final int STATUS_PENDING = 32;
+        /** TP-Status: failed. */
+        public static final int STATUS_FAILED = 64;
+
+        /**
+         * The subject of the message, if present.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SUBJECT = "subject";
+
+        /**
+         * The body of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String BODY = "body";
+
+        /**
+         * The ID of the sender of the conversation, if present.
+         * <P>Type: INTEGER (reference to item in {@code content://contacts/people})</P>
+         */
+        public static final String PERSON = "person";
+
+        /**
+         * The protocol identifier code.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String PROTOCOL = "protocol";
+
+        /**
+         * Is the {@code TP-Reply-Path} flag set?
+         * <P>Type: BOOLEAN</P>
+         */
+        public static final String REPLY_PATH_PRESENT = "reply_path_present";
+
+        /**
+         * The service center (SC) through which to send the message, if present.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SERVICE_CENTER = "service_center";
+
+        /**
+         * Is the message locked?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String LOCKED = "locked";
+
+        /**
+         * The subscription to which the message belongs to. Its value will be
+         * < 0 if the sub id cannot be determined.
+         * <p>Type: INTEGER (long) </p>
+         */
+        public static final String SUBSCRIPTION_ID = "sub_id";
+
+        /**
+         * The MTU size of the mobile interface to which the APN connected
+         * @hide
+         */
+        public static final String MTU = "mtu";
+
+        /**
+         * Error code associated with sending or receiving this message
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ERROR_CODE = "error_code";
+
+        /**
+         * The identity of the sender of a sent message. It is
+         * usually the package name of the app which sends the message.
+         * <p class="note"><strong>Note:</strong>
+         * This column is read-only. It is set by the provider and can not be changed by apps.
+         * <p>Type: TEXT</p>
+         */
+        public static final String CREATOR = "creator";
+    }
+
+    /**
+     * Contains all text-based SMS messages.
+     */
+    public static final class Sms implements BaseColumns, TextBasedSmsColumns {
+
+        /**
+         * Not instantiable.
+         * @hide
+         */
+        private Sms() {
+        }
+
+        /**
+         * Used to determine the currently configured default SMS package.
+         * @param context context of the requesting application
+         * @return package name for the default SMS package or null
+         */
+        public static String getDefaultSmsPackage(Context context) {
+            ComponentName component = SmsApplication.getDefaultSmsApplication(context, false);
+            if (component != null) {
+                return component.getPackageName();
+            }
+            return null;
+        }
+
+        /**
+         * Return cursor for table query.
+         * @hide
+         */
+        public static Cursor query(ContentResolver cr, String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        /**
+         * Return cursor for table query.
+         * @hide
+         */
+        public static Cursor query(ContentResolver cr, String[] projection,
+                String where, String orderBy) {
+            return cr.query(CONTENT_URI, projection, where,
+                    null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        /**
+         * The {@code content://} style URL for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://sms");
+
+        /**
+         * The default sort order for this table.
+         */
+        public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+        /**
+         * Add an SMS to the given URI.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the pseudo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @return the URI for the new message
+         * @hide
+         */
+        public static Uri addMessageToUri(ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport) {
+            return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+                    resolver, uri, address, body, subject, date, read, deliveryReport, -1L);
+        }
+
+        /**
+         * Add an SMS to the given URI.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the psuedo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @param subId the subscription which the message belongs to
+         * @return the URI for the new message
+         * @hide
+         */
+        public static Uri addMessageToUri(int subId, ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport) {
+            return addMessageToUri(subId, resolver, uri, address, body, subject,
+                    date, read, deliveryReport, -1L);
+        }
+
+        /**
+         * Add an SMS to the given URI with the specified thread ID.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the pseudo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @param threadId the thread_id of the message
+         * @return the URI for the new message
+         * @hide
+         */
+        public static Uri addMessageToUri(ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport, long threadId) {
+            return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+                    resolver, uri, address, body, subject,
+                    date, read, deliveryReport, threadId);
+        }
+
+        /**
+         * Add an SMS to the given URI with thread_id specified.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the psuedo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @param threadId the thread_id of the message
+         * @param subId the subscription which the message belongs to
+         * @return the URI for the new message
+         * @hide
+         */
+        public static Uri addMessageToUri(int subId, ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport, long threadId) {
+            ContentValues values = new ContentValues(8);
+            Rlog.v(TAG,"Telephony addMessageToUri sub id: " + subId);
+
+            values.put(SUBSCRIPTION_ID, subId);
+            values.put(ADDRESS, address);
+            if (date != null) {
+                values.put(DATE, date);
+            }
+            values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
+            values.put(SUBJECT, subject);
+            values.put(BODY, body);
+            if (deliveryReport) {
+                values.put(STATUS, STATUS_PENDING);
+            }
+            if (threadId != -1L) {
+                values.put(THREAD_ID, threadId);
+            }
+            return resolver.insert(uri, values);
+        }
+
+        /**
+         * Move a message to the given folder.
+         *
+         * @param context the context to use
+         * @param uri the message to move
+         * @param folder the folder to move to
+         * @return true if the operation succeeded
+         * @hide
+         */
+        public static boolean moveMessageToFolder(Context context,
+                Uri uri, int folder, int error) {
+            if (uri == null) {
+                return false;
+            }
+
+            boolean markAsUnread = false;
+            boolean markAsRead = false;
+            switch(folder) {
+            case MESSAGE_TYPE_INBOX:
+            case MESSAGE_TYPE_DRAFT:
+                break;
+            case MESSAGE_TYPE_OUTBOX:
+            case MESSAGE_TYPE_SENT:
+                markAsRead = true;
+                break;
+            case MESSAGE_TYPE_FAILED:
+            case MESSAGE_TYPE_QUEUED:
+                markAsUnread = true;
+                break;
+            default:
+                return false;
+            }
+
+            ContentValues values = new ContentValues(3);
+
+            values.put(TYPE, folder);
+            if (markAsUnread) {
+                values.put(READ, 0);
+            } else if (markAsRead) {
+                values.put(READ, 1);
+            }
+            values.put(ERROR_CODE, error);
+
+            return 1 == SqliteWrapper.update(context, context.getContentResolver(),
+                            uri, values, null, null);
+        }
+
+        /**
+         * Returns true iff the folder (message type) identifies an
+         * outgoing message.
+         * @hide
+         */
+        public static boolean isOutgoingFolder(int messageType) {
+            return  (messageType == MESSAGE_TYPE_FAILED)
+                    || (messageType == MESSAGE_TYPE_OUTBOX)
+                    || (messageType == MESSAGE_TYPE_SENT)
+                    || (messageType == MESSAGE_TYPE_QUEUED);
+        }
+
+        /**
+         * Contains all text-based SMS messages in the SMS app inbox.
+         */
+        public static final class Inbox implements BaseColumns, TextBasedSmsColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Inbox() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri CONTENT_URI = Uri.parse("content://sms/inbox");
+
+            /**
+             * The default sort order for this table.
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the pseudo-subject of the message
+             * @param date the timestamp for the message
+             * @param read true if the message has been read, false if not
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date,
+                    boolean read) {
+                return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+                        resolver, CONTENT_URI, address, body, subject, date, read, false);
+            }
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param read true if the message has been read, false if not
+             * @param subId the subscription which the message belongs to
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(int subId, ContentResolver resolver,
+                    String address, String body, String subject, Long date, boolean read) {
+                return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+                        subject, date, read, false);
+            }
+        }
+
+        /**
+         * Contains all sent text-based SMS messages in the SMS app.
+         */
+        public static final class Sent implements BaseColumns, TextBasedSmsColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Sent() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri CONTENT_URI = Uri.parse("content://sms/sent");
+
+            /**
+             * The default sort order for this table.
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the pseudo-subject of the message
+             * @param date the timestamp for the message
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+                        resolver, CONTENT_URI, address, body, subject, date, true, false);
+            }
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param subId the subscription which the message belongs to
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(int subId, ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+                        subject, date, true, false);
+            }
+        }
+
+        /**
+         * Contains all sent text-based SMS messages in the SMS app.
+         */
+        public static final class Draft implements BaseColumns, TextBasedSmsColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Draft() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri CONTENT_URI = Uri.parse("content://sms/draft");
+
+           /**
+            * @hide
+            */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+                        resolver, CONTENT_URI, address, body, subject, date, true, false);
+            }
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param subId the subscription which the message belongs to
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(int subId, ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+                        subject, date, true, false);
+            }
+
+            /**
+             * The default sort order for this table.
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all pending outgoing text-based SMS messages.
+         */
+        public static final class Outbox implements BaseColumns, TextBasedSmsColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Outbox() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri CONTENT_URI = Uri.parse("content://sms/outbox");
+
+            /**
+             * The default sort order for this table.
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * Add an SMS to the outbox.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the pseudo-subject of the message
+             * @param date the timestamp for the message
+             * @param deliveryReport whether a delivery report was requested for the message
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date,
+                    boolean deliveryReport, long threadId) {
+                return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+                        resolver, CONTENT_URI, address, body, subject, date,
+                        true, deliveryReport, threadId);
+            }
+
+            /**
+             * Add an SMS to the Out box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param deliveryReport whether a delivery report was requested for the message
+             * @param subId the subscription which the message belongs to
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(int subId, ContentResolver resolver,
+                    String address, String body, String subject, Long date,
+                    boolean deliveryReport, long threadId) {
+                return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+                        subject, date, true, deliveryReport, threadId);
+            }
+        }
+
+        /**
+         * Contains all sent text-based SMS messages in the SMS app.
+         */
+        public static final class Conversations
+                implements BaseColumns, TextBasedSmsColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Conversations() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri CONTENT_URI = Uri.parse("content://sms/conversations");
+
+            /**
+             * The default sort order for this table.
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+            /**
+             * The first 45 characters of the body of the message.
+             * <P>Type: TEXT</P>
+             */
+            public static final String SNIPPET = "snippet";
+
+            /**
+             * The number of messages in the conversation.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MESSAGE_COUNT = "msg_count";
+        }
+
+        /**
+         * Contains constants for SMS related Intents that are broadcast.
+         */
+        public static final class Intents {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Intents() {
+            }
+
+            /**
+             * Set by BroadcastReceiver to indicate that the message was handled
+             * successfully.
+             */
+            public static final int RESULT_SMS_HANDLED = 1;
+
+            /**
+             * Set by BroadcastReceiver to indicate a generic error while
+             * processing the message.
+             */
+            public static final int RESULT_SMS_GENERIC_ERROR = 2;
+
+            /**
+             * Set by BroadcastReceiver to indicate insufficient memory to store
+             * the message.
+             */
+            public static final int RESULT_SMS_OUT_OF_MEMORY = 3;
+
+            /**
+             * Set by BroadcastReceiver to indicate that the message, while
+             * possibly valid, is of a format or encoding that is not
+             * supported.
+             */
+            public static final int RESULT_SMS_UNSUPPORTED = 4;
+
+            /**
+             * Set by BroadcastReceiver to indicate a duplicate incoming message.
+             */
+            public static final int RESULT_SMS_DUPLICATED = 5;
+
+            /**
+             * Activity action: Ask the user to change the default
+             * SMS application. This will show a dialog that asks the
+             * user whether they want to replace the current default
+             * SMS application with the one specified in
+             * {@link #EXTRA_PACKAGE_NAME}.
+             */
+            @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+            public static final String ACTION_CHANGE_DEFAULT =
+                    "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+
+            /**
+             * The PackageName string passed in as an
+             * extra for {@link #ACTION_CHANGE_DEFAULT}
+             *
+             * @see #ACTION_CHANGE_DEFAULT
+             */
+            public static final String EXTRA_PACKAGE_NAME = "package";
+
+            /**
+             * Broadcast Action: A new text-based SMS message has been received
+             * by the device. This intent will only be delivered to the default
+             * sms app. That app is responsible for writing the message and notifying
+             * the user. The intent will have the following extra values:</p>
+             *
+             * <ul>
+             *   <li><em>"pdus"</em> - An Object[] of byte[]s containing the PDUs
+             *   that make up the message.</li>
+             *   <li><em>"format"</em> - A String describing the format of the PDUs. It can
+             *   be either "3gpp" or "3gpp2".</li>
+             *   <li><em>"subscription"</em> - An optional long value of the subscription id which
+             *   received the message.</li>
+             *   <li><em>"slot"</em> - An optional int value of the SIM slot containing the
+             *   subscription.</li>
+             *   <li><em>"phone"</em> - An optional int value of the phone id associated with the
+             *   subscription.</li>
+             *   <li><em>"errorCode"</em> - An optional int error code associated with receiving
+             *   the message.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             *
+             * <p class="note"><strong>Note:</strong>
+             * The broadcast receiver that filters for this intent must declare
+             * {@link android.Manifest.permission#BROADCAST_SMS} as a required permission in
+             * the <a href="{@docRoot}guide/topics/manifest/receiver-element.html">{@code
+             * <receiver>}</a> tag.
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_DELIVER_ACTION =
+                    "android.provider.Telephony.SMS_DELIVER";
+
+            /**
+             * Broadcast Action: A new text-based SMS message has been received
+             * by the device. This intent will be delivered to all registered
+             * receivers as a notification. These apps are not expected to write the
+             * message or notify the user. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>"pdus"</em> - An Object[] of byte[]s containing the PDUs
+             *   that make up the message.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_RECEIVED_ACTION =
+                    "android.provider.Telephony.SMS_RECEIVED";
+
+            /**
+             * Broadcast Action: A new data based SMS message has been received
+             * by the device. This intent will be delivered to all registered
+             * receivers as a notification. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>"pdus"</em> - An Object[] of byte[]s containing the PDUs
+             *   that make up the message.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String DATA_SMS_RECEIVED_ACTION =
+                    "android.intent.action.DATA_SMS_RECEIVED";
+
+            /**
+             * Broadcast Action: A new WAP PUSH message has been received by the
+             * device. This intent will only be delivered to the default
+             * sms app. That app is responsible for writing the message and notifying
+             * the user. The intent will have the following extra values:</p>
+             *
+             * <ul>
+             *   <li><em>"transactionId"</em> - (Integer) The WAP transaction ID</li>
+             *   <li><em>"pduType"</em> - (Integer) The WAP PDU type</li>
+             *   <li><em>"header"</em> - (byte[]) The header of the message</li>
+             *   <li><em>"data"</em> - (byte[]) The data payload of the message</li>
+             *   <li><em>"contentTypeParameters" </em>
+             *   -(HashMap&lt;String,String&gt;) Any parameters associated with the content type
+             *   (decoded from the WSP Content-Type header)</li>
+             *   <li><em>"subscription"</em> - An optional long value of the subscription id which
+             *   received the message.</li>
+             *   <li><em>"slot"</em> - An optional int value of the SIM slot containing the
+             *   subscription.</li>
+             *   <li><em>"phone"</em> - An optional int value of the phone id associated with the
+             *   subscription.</li>
+             * </ul>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             *
+             * <p>The contentTypeParameters extra value is map of content parameters keyed by
+             * their names.</p>
+             *
+             * <p>If any unassigned well-known parameters are encountered, the key of the map will
+             * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter.  If
+             * a parameter has No-Value the value in the map will be null.</p>
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_MMS} or
+             * {@link android.Manifest.permission#RECEIVE_WAP_PUSH} (depending on WAP PUSH type) to
+             * receive.</p>
+             *
+             * <p class="note"><strong>Note:</strong>
+             * The broadcast receiver that filters for this intent must declare
+             * {@link android.Manifest.permission#BROADCAST_WAP_PUSH} as a required permission in
+             * the <a href="{@docRoot}guide/topics/manifest/receiver-element.html">{@code
+             * <receiver>}</a> tag.
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String WAP_PUSH_DELIVER_ACTION =
+                    "android.provider.Telephony.WAP_PUSH_DELIVER";
+
+            /**
+             * Broadcast Action: A new WAP PUSH message has been received by the
+             * device. This intent will be delivered to all registered
+             * receivers as a notification. These apps are not expected to write the
+             * message or notify the user. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>"transactionId"</em> - (Integer) The WAP transaction ID</li>
+             *   <li><em>"pduType"</em> - (Integer) The WAP PDU type</li>
+             *   <li><em>"header"</em> - (byte[]) The header of the message</li>
+             *   <li><em>"data"</em> - (byte[]) The data payload of the message</li>
+             *   <li><em>"contentTypeParameters"</em>
+             *   - (HashMap&lt;String,String&gt;) Any parameters associated with the content type
+             *   (decoded from the WSP Content-Type header)</li>
+             * </ul>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             *
+             * <p>The contentTypeParameters extra value is map of content parameters keyed by
+             * their names.</p>
+             *
+             * <p>If any unassigned well-known parameters are encountered, the key of the map will
+             * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter.  If
+             * a parameter has No-Value the value in the map will be null.</p>
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_MMS} or
+             * {@link android.Manifest.permission#RECEIVE_WAP_PUSH} (depending on WAP PUSH type) to
+             * receive.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String WAP_PUSH_RECEIVED_ACTION =
+                    "android.provider.Telephony.WAP_PUSH_RECEIVED";
+
+            /**
+             * Broadcast Action: A new Cell Broadcast message has been received
+             * by the device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>"message"</em> - An SmsCbMessage object containing the broadcast message
+             *   data. This is not an emergency alert, so ETWS and CMAS data will be null.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_CB_RECEIVED_ACTION =
+                    "android.provider.Telephony.SMS_CB_RECEIVED";
+
+            /**
+             * Action: A SMS based carrier provision intent. Used to identify default
+             * carrier provisioning app on the device.
+             * @hide
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            @TestApi
+            public static final String SMS_CARRIER_PROVISION_ACTION =
+                    "android.provider.Telephony.SMS_CARRIER_PROVISION";
+
+            /**
+             * Broadcast Action: A new Emergency Broadcast message has been received
+             * by the device. The intent will have the following extra
+             * values:</p>
+             *
+             * <ul>
+             *   <li><em>"message"</em> - An SmsCbMessage object containing the broadcast message
+             *   data, including ETWS or CMAS warning notification info if present.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST} to
+             * receive.</p>
+             * @removed
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_EMERGENCY_CB_RECEIVED_ACTION =
+                    "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
+
+            /**
+             * Broadcast Action: A new CDMA SMS has been received containing Service Category
+             * Program Data (updates the list of enabled broadcast channels). The intent will
+             * have the following extra values:</p>
+             *
+             * <ul>
+             *   <li><em>"operations"</em> - An array of CdmaSmsCbProgramData objects containing
+             *   the service category operations (add/delete/clear) to perform.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION =
+                    "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED";
+
+            /**
+             * Broadcast Action: The SIM storage for SMS messages is full.  If
+             * space is not freed, messages targeted for the SIM (class 2) may
+             * not be saved.
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SIM_FULL_ACTION =
+                    "android.provider.Telephony.SIM_FULL";
+
+            /**
+             * Broadcast Action: An incoming SMS has been rejected by the
+             * telephony framework.  This intent is sent in lieu of any
+             * of the RECEIVED_ACTION intents.  The intent will have the
+             * following extra value:</p>
+             *
+             * <ul>
+             *   <li><em>"result"</em> - An int result code, e.g. {@link #RESULT_SMS_OUT_OF_MEMORY}
+             *   indicating the error returned to the network.</li>
+             * </ul>
+             *
+             * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_REJECTED_ACTION =
+                "android.provider.Telephony.SMS_REJECTED";
+
+            /**
+             * Broadcast Action: An incoming MMS has been downloaded. The intent is sent to all
+             * users, except for secondary users where SMS has been disabled and to managed
+             * profiles.
+             * @hide
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String MMS_DOWNLOADED_ACTION =
+                "android.provider.Telephony.MMS_DOWNLOADED";
+
+            /**
+             * Broadcast action: When the default SMS package changes,
+             * the previous default SMS package and the new default SMS
+             * package are sent this broadcast to notify them of the change.
+             * A boolean is specified in {@link #EXTRA_IS_DEFAULT_SMS_APP} to
+             * indicate whether the package is the new default SMS package.
+            */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String ACTION_DEFAULT_SMS_PACKAGE_CHANGED =
+                            "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+
+            /**
+             * The IsDefaultSmsApp boolean passed as an
+             * extra for {@link #ACTION_DEFAULT_SMS_PACKAGE_CHANGED} to indicate whether the
+             * SMS app is becoming the default SMS app or is no longer the default.
+             *
+             * @see #ACTION_DEFAULT_SMS_PACKAGE_CHANGED
+             */
+            public static final String EXTRA_IS_DEFAULT_SMS_APP =
+                    "android.provider.extra.IS_DEFAULT_SMS_APP";
+
+            /**
+             * Broadcast action: When a change is made to the SmsProvider or
+             * MmsProvider by a process other than the default SMS application,
+             * this intent is broadcast to the default SMS application so it can
+             * re-sync or update the change. The uri that was used to call the provider
+             * can be retrieved from the intent with getData(). The actual affected uris
+             * (which would depend on the selection specified) are not included.
+            */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String ACTION_EXTERNAL_PROVIDER_CHANGE =
+                          "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
+
+            /**
+             * Read the PDUs out of an {@link #SMS_RECEIVED_ACTION} or a
+             * {@link #DATA_SMS_RECEIVED_ACTION} intent.
+             *
+             * @param intent the intent to read from
+             * @return an array of SmsMessages for the PDUs
+             */
+            public static SmsMessage[] getMessagesFromIntent(Intent intent) {
+                Object[] messages;
+                try {
+                    messages = (Object[]) intent.getSerializableExtra("pdus");
+                }
+                catch (ClassCastException e) {
+                    Rlog.e(TAG, "getMessagesFromIntent: " + e);
+                    return null;
+                }
+
+                if (messages == null) {
+                    Rlog.e(TAG, "pdus does not exist in the intent");
+                    return null;
+                }
+
+                String format = intent.getStringExtra("format");
+                int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                        SubscriptionManager.getDefaultSmsSubscriptionId());
+
+                Rlog.v(TAG, " getMessagesFromIntent sub_id : " + subId);
+
+                int pduCount = messages.length;
+                SmsMessage[] msgs = new SmsMessage[pduCount];
+
+                for (int i = 0; i < pduCount; i++) {
+                    byte[] pdu = (byte[]) messages[i];
+                    msgs[i] = SmsMessage.createFromPdu(pdu, format);
+                    if (msgs[i] != null) msgs[i].setSubId(subId);
+                }
+                return msgs;
+            }
+        }
+    }
+
+    /**
+     * Base columns for tables that contain MMSs.
+     */
+    public interface BaseMmsColumns extends BaseColumns {
+
+        /** Message box: all messages. */
+        public static final int MESSAGE_BOX_ALL    = 0;
+        /** Message box: inbox. */
+        public static final int MESSAGE_BOX_INBOX  = 1;
+        /** Message box: sent messages. */
+        public static final int MESSAGE_BOX_SENT   = 2;
+        /** Message box: drafts. */
+        public static final int MESSAGE_BOX_DRAFTS = 3;
+        /** Message box: outbox. */
+        public static final int MESSAGE_BOX_OUTBOX = 4;
+        /** Message box: failed. */
+        public static final int MESSAGE_BOX_FAILED = 5;
+
+        /**
+         * The thread ID of the message.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String THREAD_ID = "thread_id";
+
+        /**
+         * The date the message was received.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * The date the message was sent.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE_SENT = "date_sent";
+
+        /**
+         * The box which the message belongs to, e.g. {@link #MESSAGE_BOX_INBOX}.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_BOX = "msg_box";
+
+        /**
+         * Has the message been read?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String READ = "read";
+
+        /**
+         * Has the message been seen by the user? The "seen" flag determines
+         * whether we need to show a new message notification.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String SEEN = "seen";
+
+        /**
+         * Does the message have only a text part (can also have a subject) with
+         * no picture, slideshow, sound, etc. parts?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String TEXT_ONLY = "text_only";
+
+        /**
+         * The {@code Message-ID} of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MESSAGE_ID = "m_id";
+
+        /**
+         * The subject of the message, if present.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SUBJECT = "sub";
+
+        /**
+         * The character set of the subject, if present.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SUBJECT_CHARSET = "sub_cs";
+
+        /**
+         * The {@code Content-Type} of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CONTENT_TYPE = "ct_t";
+
+        /**
+         * The {@code Content-Location} of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String CONTENT_LOCATION = "ct_l";
+
+        /**
+         * The expiry time of the message.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String EXPIRY = "exp";
+
+        /**
+         * The class of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MESSAGE_CLASS = "m_cls";
+
+        /**
+         * The type of the message defined by MMS spec.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_TYPE = "m_type";
+
+        /**
+         * The version of the specification that this message conforms to.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MMS_VERSION = "v";
+
+        /**
+         * The size of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_SIZE = "m_size";
+
+        /**
+         * The priority of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String PRIORITY = "pri";
+
+        /**
+         * The {@code read-report} of the message.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String READ_REPORT = "rr";
+
+        /**
+         * Is read report allowed?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String REPORT_ALLOWED = "rpt_a";
+
+        /**
+         * The {@code response-status} of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String RESPONSE_STATUS = "resp_st";
+
+        /**
+         * The {@code status} of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String STATUS = "st";
+
+        /**
+         * The {@code transaction-id} of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TRANSACTION_ID = "tr_id";
+
+        /**
+         * The {@code retrieve-status} of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String RETRIEVE_STATUS = "retr_st";
+
+        /**
+         * The {@code retrieve-text} of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RETRIEVE_TEXT = "retr_txt";
+
+        /**
+         * The character set of the retrieve-text.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String RETRIEVE_TEXT_CHARSET = "retr_txt_cs";
+
+        /**
+         * The {@code read-status} of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String READ_STATUS = "read_status";
+
+        /**
+         * The {@code content-class} of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CONTENT_CLASS = "ct_cls";
+
+        /**
+         * The {@code delivery-report} of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DELIVERY_REPORT = "d_rpt";
+
+        /**
+         * The {@code delivery-time-token} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String DELIVERY_TIME_TOKEN = "d_tm_tok";
+
+        /**
+         * The {@code delivery-time} of the message.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DELIVERY_TIME = "d_tm";
+
+        /**
+         * The {@code response-text} of the message.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RESPONSE_TEXT = "resp_txt";
+
+        /**
+         * The {@code sender-visibility} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String SENDER_VISIBILITY = "s_vis";
+
+        /**
+         * The {@code reply-charging} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String REPLY_CHARGING = "r_chg";
+
+        /**
+         * The {@code reply-charging-deadline-token} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String REPLY_CHARGING_DEADLINE_TOKEN = "r_chg_dl_tok";
+
+        /**
+         * The {@code reply-charging-deadline} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String REPLY_CHARGING_DEADLINE = "r_chg_dl";
+
+        /**
+         * The {@code reply-charging-id} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String REPLY_CHARGING_ID = "r_chg_id";
+
+        /**
+         * The {@code reply-charging-size} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String REPLY_CHARGING_SIZE = "r_chg_sz";
+
+        /**
+         * The {@code previously-sent-by} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String PREVIOUSLY_SENT_BY = "p_s_by";
+
+        /**
+         * The {@code previously-sent-date} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String PREVIOUSLY_SENT_DATE = "p_s_d";
+
+        /**
+         * The {@code store} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String STORE = "store";
+
+        /**
+         * The {@code mm-state} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String MM_STATE = "mm_st";
+
+        /**
+         * The {@code mm-flags-token} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String MM_FLAGS_TOKEN = "mm_flg_tok";
+
+        /**
+         * The {@code mm-flags} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String MM_FLAGS = "mm_flg";
+
+        /**
+         * The {@code store-status} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String STORE_STATUS = "store_st";
+
+        /**
+         * The {@code store-status-text} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String STORE_STATUS_TEXT = "store_st_txt";
+
+        /**
+         * The {@code stored} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String STORED = "stored";
+
+        /**
+         * The {@code totals} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String TOTALS = "totals";
+
+        /**
+         * The {@code mbox-totals} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String MBOX_TOTALS = "mb_t";
+
+        /**
+         * The {@code mbox-totals-token} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String MBOX_TOTALS_TOKEN = "mb_t_tok";
+
+        /**
+         * The {@code quotas} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String QUOTAS = "qt";
+
+        /**
+         * The {@code mbox-quotas} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String MBOX_QUOTAS = "mb_qt";
+
+        /**
+         * The {@code mbox-quotas-token} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String MBOX_QUOTAS_TOKEN = "mb_qt_tok";
+
+        /**
+         * The {@code message-count} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String MESSAGE_COUNT = "m_cnt";
+
+        /**
+         * The {@code start} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String START = "start";
+
+        /**
+         * The {@code distribution-indicator} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String DISTRIBUTION_INDICATOR = "d_ind";
+
+        /**
+         * The {@code element-descriptor} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String ELEMENT_DESCRIPTOR = "e_des";
+
+        /**
+         * The {@code limit} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String LIMIT = "limit";
+
+        /**
+         * The {@code recommended-retrieval-mode} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String RECOMMENDED_RETRIEVAL_MODE = "r_r_mod";
+
+        /**
+         * The {@code recommended-retrieval-mode-text} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String RECOMMENDED_RETRIEVAL_MODE_TEXT = "r_r_mod_txt";
+
+        /**
+         * The {@code status-text} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String STATUS_TEXT = "st_txt";
+
+        /**
+         * The {@code applic-id} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String APPLIC_ID = "apl_id";
+
+        /**
+         * The {@code reply-applic-id} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String REPLY_APPLIC_ID = "r_apl_id";
+
+        /**
+         * The {@code aux-applic-id} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String AUX_APPLIC_ID = "aux_apl_id";
+
+        /**
+         * The {@code drm-content} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String DRM_CONTENT = "drm_c";
+
+        /**
+         * The {@code adaptation-allowed} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String ADAPTATION_ALLOWED = "adp_a";
+
+        /**
+         * The {@code replace-id} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String REPLACE_ID = "repl_id";
+
+        /**
+         * The {@code cancel-id} of the message.
+         * <P>Type: TEXT</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String CANCEL_ID = "cl_id";
+
+        /**
+         * The {@code cancel-status} of the message.
+         * <P>Type: INTEGER</P>
+         * @deprecated this column is no longer supported.
+         * @hide
+         */
+        @Deprecated
+        public static final String CANCEL_STATUS = "cl_st";
+
+        /**
+         * Is the message locked?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String LOCKED = "locked";
+
+        /**
+         * The subscription to which the message belongs to. Its value will be
+         * < 0 if the sub id cannot be determined.
+         * <p>Type: INTEGER (long)</p>
+         */
+        public static final String SUBSCRIPTION_ID = "sub_id";
+
+        /**
+         * The identity of the sender of a sent message. It is
+         * usually the package name of the app which sends the message.
+         * <p class="note"><strong>Note:</strong>
+         * This column is read-only. It is set by the provider and can not be changed by apps.
+         * <p>Type: TEXT</p>
+         */
+        public static final String CREATOR = "creator";
+    }
+
+    /**
+     * Columns for the "canonical_addresses" table used by MMS and SMS.
+     */
+    public interface CanonicalAddressesColumns extends BaseColumns {
+        /**
+         * An address used in MMS or SMS.  Email addresses are
+         * converted to lower case and are compared by string
+         * equality.  Other addresses are compared using
+         * PHONE_NUMBERS_EQUAL.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ADDRESS = "address";
+    }
+
+    /**
+     * Columns for the "threads" table used by MMS and SMS.
+     */
+    public interface ThreadsColumns extends BaseColumns {
+
+        /**
+         * The date at which the thread was created.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATE = "date";
+
+        /**
+         * A string encoding of the recipient IDs of the recipients of
+         * the message, in numerical order and separated by spaces.
+         * <P>Type: TEXT</P>
+         */
+        public static final String RECIPIENT_IDS = "recipient_ids";
+
+        /**
+         * The message count of the thread.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_COUNT = "message_count";
+
+        /**
+         * Indicates whether all messages of the thread have been read.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String READ = "read";
+
+        /**
+         * The snippet of the latest message in the thread.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SNIPPET = "snippet";
+
+        /**
+         * The charset of the snippet.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SNIPPET_CHARSET = "snippet_cs";
+
+        /**
+         * Type of the thread, either {@link Threads#COMMON_THREAD} or
+         * {@link Threads#BROADCAST_THREAD}.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String TYPE = "type";
+
+        /**
+         * Indicates whether there is a transmission error in the thread.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ERROR = "error";
+
+        /**
+         * Indicates whether this thread contains any attachments.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String HAS_ATTACHMENT = "has_attachment";
+
+        /**
+         * If the thread is archived
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String ARCHIVED = "archived";
+    }
+
+    /**
+     * Helper functions for the "threads" table used by MMS and SMS.
+     */
+    public static final class Threads implements ThreadsColumns {
+
+        private static final String[] ID_PROJECTION = { BaseColumns._ID };
+
+        /**
+         * Private {@code content://} style URL for this table. Used by
+         * {@link #getOrCreateThreadId(android.content.Context, java.util.Set)}.
+         */
+        private static final Uri THREAD_ID_CONTENT_URI = Uri.parse(
+                "content://mms-sms/threadID");
+
+        /**
+         * The {@code content://} style URL for this table, by conversation.
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                MmsSms.CONTENT_URI, "conversations");
+
+        /**
+         * The {@code content://} style URL for this table, for obsolete threads.
+         */
+        public static final Uri OBSOLETE_THREADS_URI = Uri.withAppendedPath(
+                CONTENT_URI, "obsolete");
+
+        /** Thread type: common thread. */
+        public static final int COMMON_THREAD    = 0;
+
+        /** Thread type: broadcast thread. */
+        public static final int BROADCAST_THREAD = 1;
+
+        /**
+         * Not instantiable.
+         * @hide
+         */
+        private Threads() {
+        }
+
+        /**
+         * This is a single-recipient version of {@code getOrCreateThreadId}.
+         * It's convenient for use with SMS messages.
+         * @param context the context object to use.
+         * @param recipient the recipient to send to.
+         */
+        public static long getOrCreateThreadId(Context context, String recipient) {
+            Set<String> recipients = new HashSet<String>();
+
+            recipients.add(recipient);
+            return getOrCreateThreadId(context, recipients);
+        }
+
+        /**
+         * Given the recipients list and subject of an unsaved message,
+         * return its thread ID.  If the message starts a new thread,
+         * allocate a new thread ID.  Otherwise, use the appropriate
+         * existing thread ID.
+         *
+         * <p>Find the thread ID of the same set of recipients (in any order,
+         * without any additions). If one is found, return it. Otherwise,
+         * return a unique thread ID.</p>
+         */
+        public static long getOrCreateThreadId(
+                Context context, Set<String> recipients) {
+            Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
+
+            for (String recipient : recipients) {
+                if (Mms.isEmailAddress(recipient)) {
+                    recipient = Mms.extractAddrSpec(recipient);
+                }
+
+                uriBuilder.appendQueryParameter("recipient", recipient);
+            }
+
+            Uri uri = uriBuilder.build();
+            //if (DEBUG) Rlog.v(TAG, "getOrCreateThreadId uri: " + uri);
+
+            Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
+                    uri, ID_PROJECTION, null, null, null);
+            if (cursor != null) {
+                try {
+                    if (cursor.moveToFirst()) {
+                        return cursor.getLong(0);
+                    } else {
+                        Rlog.e(TAG, "getOrCreateThreadId returned no rows!");
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+
+            Rlog.e(TAG, "getOrCreateThreadId failed with " + recipients.size() + " recipients");
+            throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
+        }
+    }
+
+    /**
+     * Contains all MMS messages.
+     */
+    public static final class Mms implements BaseMmsColumns {
+
+        /**
+         * Not instantiable.
+         * @hide
+         */
+        private Mms() {
+        }
+
+        /**
+         * The {@code content://} URI for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://mms");
+
+        /**
+         * Content URI for getting MMS report requests.
+         */
+        public static final Uri REPORT_REQUEST_URI = Uri.withAppendedPath(
+                                            CONTENT_URI, "report-request");
+
+        /**
+         * Content URI for getting MMS report status.
+         */
+        public static final Uri REPORT_STATUS_URI = Uri.withAppendedPath(
+                                            CONTENT_URI, "report-status");
+
+        /**
+         * The default sort order for this table.
+         */
+        public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+        /**
+         * Regex pattern for names and email addresses.
+         * <ul>
+         *     <li><em>mailbox</em> = {@code name-addr}</li>
+         *     <li><em>name-addr</em> = {@code [display-name] angle-addr}</li>
+         *     <li><em>angle-addr</em> = {@code [CFWS] "<" addr-spec ">" [CFWS]}</li>
+         * </ul>
+         * @hide
+         */
+        public static final Pattern NAME_ADDR_EMAIL_PATTERN =
+                Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
+
+        /**
+         * Helper method to query this table.
+         * @hide
+         */
+        public static Cursor query(
+                ContentResolver cr, String[] projection) {
+            return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+        }
+
+        /**
+         * Helper method to query this table.
+         * @hide
+         */
+        public static Cursor query(
+                ContentResolver cr, String[] projection,
+                String where, String orderBy) {
+            return cr.query(CONTENT_URI, projection,
+                    where, null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
+        /**
+         * Helper method to extract email address from address string.
+         * @hide
+         */
+        public static String extractAddrSpec(String address) {
+            Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
+
+            if (match.matches()) {
+                return match.group(2);
+            }
+            return address;
+        }
+
+        /**
+         * Is the specified address an email address?
+         *
+         * @param address the input address to test
+         * @return true if address is an email address; false otherwise.
+         * @hide
+         */
+        public static boolean isEmailAddress(String address) {
+            if (TextUtils.isEmpty(address)) {
+                return false;
+            }
+
+            String s = extractAddrSpec(address);
+            Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
+            return match.matches();
+        }
+
+        /**
+         * Is the specified number a phone number?
+         *
+         * @param number the input number to test
+         * @return true if number is a phone number; false otherwise.
+         * @hide
+         */
+        public static boolean isPhoneNumber(String number) {
+            if (TextUtils.isEmpty(number)) {
+                return false;
+            }
+
+            Matcher match = Patterns.PHONE.matcher(number);
+            return match.matches();
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app inbox.
+         */
+        public static final class Inbox implements BaseMmsColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Inbox() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/inbox");
+
+            /**
+             * The default sort order for this table.
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app sent folder.
+         */
+        public static final class Sent implements BaseMmsColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Sent() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/sent");
+
+            /**
+             * The default sort order for this table.
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app drafts folder.
+         */
+        public static final class Draft implements BaseMmsColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Draft() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/drafts");
+
+            /**
+             * The default sort order for this table.
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains all MMS messages in the MMS app outbox.
+         */
+        public static final class Outbox implements BaseMmsColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Outbox() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri
+                    CONTENT_URI = Uri.parse("content://mms/outbox");
+
+            /**
+             * The default sort order for this table.
+             */
+            public static final String DEFAULT_SORT_ORDER = "date DESC";
+        }
+
+        /**
+         * Contains address information for an MMS message.
+         */
+        public static final class Addr implements BaseColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Addr() {
+            }
+
+            /**
+             * The ID of MM which this address entry belongs to.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String MSG_ID = "msg_id";
+
+            /**
+             * The ID of contact entry in Phone Book.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String CONTACT_ID = "contact_id";
+
+            /**
+             * The address text.
+             * <P>Type: TEXT</P>
+             */
+            public static final String ADDRESS = "address";
+
+            /**
+             * Type of address: must be one of {@code PduHeaders.BCC},
+             * {@code PduHeaders.CC}, {@code PduHeaders.FROM}, {@code PduHeaders.TO}.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String TYPE = "type";
+
+            /**
+             * Character set of this entry (MMS charset value).
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CHARSET = "charset";
+        }
+
+        /**
+         * Contains message parts.
+         */
+        public static final class Part implements BaseColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Part() {
+            }
+
+            /**
+             * The identifier of the message which this part belongs to.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MSG_ID = "mid";
+
+            /**
+             * The order of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String SEQ = "seq";
+
+            /**
+             * The content type of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CONTENT_TYPE = "ct";
+
+            /**
+             * The name of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String NAME = "name";
+
+            /**
+             * The charset of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CHARSET = "chset";
+
+            /**
+             * The file name of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String FILENAME = "fn";
+
+            /**
+             * The content disposition of the part.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CONTENT_DISPOSITION = "cd";
+
+            /**
+             * The content ID of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CONTENT_ID = "cid";
+
+            /**
+             * The content location of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CONTENT_LOCATION = "cl";
+
+            /**
+             * The start of content-type of the message.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String CT_START = "ctt_s";
+
+            /**
+             * The type of content-type of the message.
+             * <P>Type: TEXT</P>
+             */
+            public static final String CT_TYPE = "ctt_t";
+
+            /**
+             * The location (on filesystem) of the binary data of the part.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String _DATA = "_data";
+
+            /**
+             * The message text.
+             * <P>Type: TEXT</P>
+             */
+            public static final String TEXT = "text";
+        }
+
+        /**
+         * Message send rate table.
+         */
+        public static final class Rate {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Rate() {
+            }
+
+            /**
+             * The {@code content://} style URL for this table.
+             */
+            public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                    Mms.CONTENT_URI, "rate");
+
+            /**
+             * When a message was successfully sent.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String SENT_TIME = "sent_time";
+        }
+
+        /**
+         * Intents class.
+         */
+        public static final class Intents {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private Intents() {
+            }
+
+            /**
+             * Indicates that the contents of specified URIs were changed.
+             * The application which is showing or caching these contents
+             * should be updated.
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String CONTENT_CHANGED_ACTION
+                    = "android.intent.action.CONTENT_CHANGED";
+
+            /**
+             * An extra field which stores the URI of deleted contents.
+             */
+            public static final String DELETED_CONTENTS = "deleted_contents";
+        }
+    }
+
+    /**
+     * Contains all MMS and SMS messages.
+     */
+    public static final class MmsSms implements BaseColumns {
+
+        /**
+         * Not instantiable.
+         * @hide
+         */
+        private MmsSms() {
+        }
+
+        /**
+         * The column to distinguish SMS and MMS messages in query results.
+         */
+        public static final String TYPE_DISCRIMINATOR_COLUMN =
+                "transport_type";
+
+        /**
+         * The {@code content://} style URL for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://mms-sms/");
+
+        /**
+         * The {@code content://} style URL for this table, by conversation.
+         */
+        public static final Uri CONTENT_CONVERSATIONS_URI = Uri.parse(
+                "content://mms-sms/conversations");
+
+        /**
+         * The {@code content://} style URL for this table, by phone number.
+         */
+        public static final Uri CONTENT_FILTER_BYPHONE_URI = Uri.parse(
+                "content://mms-sms/messages/byphone");
+
+        /**
+         * The {@code content://} style URL for undelivered messages in this table.
+         */
+        public static final Uri CONTENT_UNDELIVERED_URI = Uri.parse(
+                "content://mms-sms/undelivered");
+
+        /**
+         * The {@code content://} style URL for draft messages in this table.
+         */
+        public static final Uri CONTENT_DRAFT_URI = Uri.parse(
+                "content://mms-sms/draft");
+
+        /**
+         * The {@code content://} style URL for locked messages in this table.
+         */
+        public static final Uri CONTENT_LOCKED_URI = Uri.parse(
+                "content://mms-sms/locked");
+
+        /**
+         * Pass in a query parameter called "pattern" which is the text to search for.
+         * The sort order is fixed to be: {@code thread_id ASC, date DESC}.
+         */
+        public static final Uri SEARCH_URI = Uri.parse(
+                "content://mms-sms/search");
+
+        // Constants for message protocol types.
+
+        /** SMS protocol type. */
+        public static final int SMS_PROTO = 0;
+
+        /** MMS protocol type. */
+        public static final int MMS_PROTO = 1;
+
+        // Constants for error types of pending messages.
+
+        /** Error type: no error. */
+        public static final int NO_ERROR                      = 0;
+
+        /** Error type: generic transient error. */
+        public static final int ERR_TYPE_GENERIC              = 1;
+
+        /** Error type: SMS protocol transient error. */
+        public static final int ERR_TYPE_SMS_PROTO_TRANSIENT  = 2;
+
+        /** Error type: MMS protocol transient error. */
+        public static final int ERR_TYPE_MMS_PROTO_TRANSIENT  = 3;
+
+        /** Error type: transport failure. */
+        public static final int ERR_TYPE_TRANSPORT_FAILURE    = 4;
+
+        /** Error type: permanent error (along with all higher error values). */
+        public static final int ERR_TYPE_GENERIC_PERMANENT    = 10;
+
+        /** Error type: SMS protocol permanent error. */
+        public static final int ERR_TYPE_SMS_PROTO_PERMANENT  = 11;
+
+        /** Error type: MMS protocol permanent error. */
+        public static final int ERR_TYPE_MMS_PROTO_PERMANENT  = 12;
+
+        /**
+         * Contains pending messages info.
+         */
+        public static final class PendingMessages implements BaseColumns {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private PendingMessages() {
+            }
+
+            public static final Uri CONTENT_URI = Uri.withAppendedPath(
+                    MmsSms.CONTENT_URI, "pending");
+
+            /**
+             * The type of transport protocol (MMS or SMS).
+             * <P>Type: INTEGER</P>
+             */
+            public static final String PROTO_TYPE = "proto_type";
+
+            /**
+             * The ID of the message to be sent or downloaded.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String MSG_ID = "msg_id";
+
+            /**
+             * The type of the message to be sent or downloaded.
+             * This field is only valid for MM. For SM, its value is always set to 0.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MSG_TYPE = "msg_type";
+
+            /**
+             * The type of the error code.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String ERROR_TYPE = "err_type";
+
+            /**
+             * The error code of sending/retrieving process.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String ERROR_CODE = "err_code";
+
+            /**
+             * How many times we tried to send or download the message.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String RETRY_INDEX = "retry_index";
+
+            /**
+             * The time to do next retry.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String DUE_TIME = "due_time";
+
+            /**
+             * The time we last tried to send or download the message.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String LAST_TRY = "last_try";
+
+            /**
+             * The subscription to which the message belongs to. Its value will be
+             * < 0 if the sub id cannot be determined.
+             * <p>Type: INTEGER (long) </p>
+             */
+            public static final String SUBSCRIPTION_ID = "pending_sub_id";
+        }
+
+        /**
+         * Words table used by provider for full-text searches.
+         * @hide
+         */
+        public static final class WordsTable {
+
+            /**
+             * Not instantiable.
+             * @hide
+             */
+            private WordsTable() {}
+
+            /**
+             * Primary key.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String ID = "_id";
+
+            /**
+             * Source row ID.
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String SOURCE_ROW_ID = "source_id";
+
+            /**
+             * Table ID (either 1 or 2).
+             * <P>Type: INTEGER</P>
+             */
+            public static final String TABLE_ID = "table_to_use";
+
+            /**
+             * The words to index.
+             * <P>Type: TEXT</P>
+             */
+            public static final String INDEXED_TEXT = "index_text";
+        }
+    }
+
+    /**
+     * Carriers class contains information about APNs, including MMSC information.
+     */
+    public static final class Carriers implements BaseColumns {
+
+        /**
+         * Not instantiable.
+         * @hide
+         */
+        private Carriers() {}
+
+        /**
+         * The {@code content://} style URL for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://telephony/carriers");
+
+        /**
+         * The default sort order for this table.
+         */
+        public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+        /**
+         * Entry name.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NAME = "name";
+
+        /**
+         * APN name.
+         * <P>Type: TEXT</P>
+         */
+        public static final String APN = "apn";
+
+        /**
+         * Proxy address.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PROXY = "proxy";
+
+        /**
+         * Proxy port.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PORT = "port";
+
+        /**
+         * MMS proxy address.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MMSPROXY = "mmsproxy";
+
+        /**
+         * MMS proxy port.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MMSPORT = "mmsport";
+
+        /**
+         * Server address.
+         * <P>Type: TEXT</P>
+         */
+        public static final String SERVER = "server";
+
+        /**
+         * APN username.
+         * <P>Type: TEXT</P>
+         */
+        public static final String USER = "user";
+
+        /**
+         * APN password.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PASSWORD = "password";
+
+        /**
+         * MMSC URL.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MMSC = "mmsc";
+
+        /**
+         * Mobile Country Code (MCC).
+         * <P>Type: TEXT</P>
+         */
+        public static final String MCC = "mcc";
+
+        /**
+         * Mobile Network Code (MNC).
+         * <P>Type: TEXT</P>
+         */
+        public static final String MNC = "mnc";
+
+        /**
+         * Numeric operator ID (as String). Usually {@code MCC + MNC}.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NUMERIC = "numeric";
+
+        /**
+         * Authentication type.
+         * <P>Type:  INTEGER</P>
+         */
+        public static final String AUTH_TYPE = "authtype";
+
+        /**
+         * Comma-delimited list of APN types.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TYPE = "type";
+
+        /**
+         * The protocol to use to connect to this APN.
+         *
+         * One of the {@code PDP_type} values in TS 27.007 section 10.1.1.
+         * For example: {@code IP}, {@code IPV6}, {@code IPV4V6}, or {@code PPP}.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PROTOCOL = "protocol";
+
+        /**
+         * The protocol to use to connect to this APN when roaming.
+         * The syntax is the same as protocol.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ROAMING_PROTOCOL = "roaming_protocol";
+
+        /**
+         * Is this the current APN?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String CURRENT = "current";
+
+        /**
+         * Is this APN enabled?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String CARRIER_ENABLED = "carrier_enabled";
+
+        /**
+         * Radio Access Technology info.
+         * To check what values are allowed, refer to {@link android.telephony.ServiceState}.
+         * This should be spread to other technologies,
+         * but is currently only used for LTE (14) and eHRPD (13).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String BEARER = "bearer";
+
+        /**
+         * Radio Access Technology bitmask.
+         * To check what values can be contained, refer to {@link android.telephony.ServiceState}.
+         * 0 indicates all techs otherwise first bit refers to RAT/bearer 1, second bit refers to
+         * RAT/bearer 2 and so on.
+         * Bitmask for a radio tech R is (1 << (R - 1))
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String BEARER_BITMASK = "bearer_bitmask";
+
+        /**
+         * MVNO type:
+         * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MVNO_TYPE = "mvno_type";
+
+        /**
+         * MVNO data.
+         * Use the following examples.
+         * <ul>
+         *     <li>SPN: A MOBILE, BEN NL, ...</li>
+         *     <li>IMSI: 302720x94, 2060188, ...</li>
+         *     <li>GID: 4E, 33, ...</li>
+         * </ul>
+         * <P>Type: TEXT</P>
+         */
+        public static final String MVNO_MATCH_DATA = "mvno_match_data";
+
+        /**
+         * The subscription to which the APN belongs to
+         * <p>Type: INTEGER (long) </p>
+         */
+        public static final String SUBSCRIPTION_ID = "sub_id";
+
+        /**
+         * The profile_id to which the APN saved in modem
+         * <p>Type: INTEGER</p>
+         *@hide
+         */
+        public static final String PROFILE_ID = "profile_id";
+
+        /**
+         * Is the apn setting to be set in modem
+         * <P>Type: INTEGER (boolean)</P>
+         *@hide
+         */
+        public static final String MODEM_COGNITIVE = "modem_cognitive";
+
+        /**
+         * The max connections of this apn
+         * <p>Type: INTEGER</p>
+         *@hide
+         */
+        public static final String MAX_CONNS = "max_conns";
+
+        /**
+         * The wait time for retry of the apn
+         * <p>Type: INTEGER</p>
+         *@hide
+         */
+        public static final String WAIT_TIME = "wait_time";
+
+        /**
+         * The time to limit max connection for the apn
+         * <p>Type: INTEGER</p>
+         *@hide
+         */
+        public static final String MAX_CONNS_TIME = "max_conns_time";
+
+        /**
+         * The MTU size of the mobile interface to  which the APN connected
+         * <p>Type: INTEGER </p>
+         * @hide
+         */
+        public static final String MTU = "mtu";
+
+        /**
+         * Is this APN added/edited/deleted by a user or carrier?
+         * <p>Type: INTEGER </p>
+         * @hide
+         */
+        public static final String EDITED = "edited";
+
+        /**
+         * Is this APN visible to the user?
+         * <p>Type: INTEGER (boolean) </p>
+         * @hide
+         */
+        public static final String USER_VISIBLE = "user_visible";
+
+        /**
+         * Following are possible values for the EDITED field
+         * @hide
+         */
+        public static final int UNEDITED = 0;
+        /**
+         *  @hide
+         */
+        public static final int USER_EDITED = 1;
+        /**
+         *  @hide
+         */
+        public static final int USER_DELETED = 2;
+        /**
+         * DELETED_BUT_PRESENT is an intermediate value used to indicate that an entry deleted
+         * by the user is still present in the new APN database and therefore must remain tagged
+         * as user deleted rather than completely removed from the database
+         * @hide
+         */
+        public static final int USER_DELETED_BUT_PRESENT_IN_XML = 3;
+        /**
+         *  @hide
+         */
+        public static final int CARRIER_EDITED = 4;
+        /**
+         * CARRIER_DELETED values are currently not used as there is no usecase. If they are used,
+         * delete() will have to change accordingly. Currently it is hardcoded to USER_DELETED.
+         * @hide
+         */
+        public static final int CARRIER_DELETED = 5;
+        /**
+         *  @hide
+         */
+        public static final int CARRIER_DELETED_BUT_PRESENT_IN_XML = 6;
+    }
+
+    /**
+     * Contains received SMS cell broadcast messages.
+     * @hide
+     */
+    public static final class CellBroadcasts implements BaseColumns {
+
+        /**
+         * Not instantiable.
+         * @hide
+         */
+        private CellBroadcasts() {}
+
+        /**
+         * The {@code content://} URI for this table.
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts");
+
+        /**
+         * Message geographical scope.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+
+        /**
+         * Message serial number.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SERIAL_NUMBER = "serial_number";
+
+        /**
+         * PLMN of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID} uniquely identifies
+         * a broadcast for duplicate detection purposes.
+         * <P>Type: TEXT</P>
+         */
+        public static final String PLMN = "plmn";
+
+        /**
+         * Location Area (GSM) or Service Area (UMTS) of broadcast sender. Unused for CDMA.
+         * Only included if Geographical Scope of message is not PLMN wide (01).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String LAC = "lac";
+
+        /**
+         * Cell ID of message sender (GSM/UMTS). Unused for CDMA. Only included when the
+         * Geographical Scope of message is cell wide (00 or 11).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CID = "cid";
+
+        /**
+         * Message code. <em>OBSOLETE: merged into SERIAL_NUMBER.</em>
+         * <P>Type: INTEGER</P>
+         */
+        public static final String V1_MESSAGE_CODE = "message_code";
+
+        /**
+         * Message identifier. <em>OBSOLETE: renamed to SERVICE_CATEGORY.</em>
+         * <P>Type: INTEGER</P>
+         */
+        public static final String V1_MESSAGE_IDENTIFIER = "message_id";
+
+        /**
+         * Service category (GSM/UMTS: message identifier; CDMA: service category).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SERVICE_CATEGORY = "service_category";
+
+        /**
+         * Message language code.
+         * <P>Type: TEXT</P>
+         */
+        public static final String LANGUAGE_CODE = "language";
+
+        /**
+         * Message body.
+         * <P>Type: TEXT</P>
+         */
+        public static final String MESSAGE_BODY = "body";
+
+        /**
+         * Message delivery time.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DELIVERY_TIME = "date";
+
+        /**
+         * Has the message been viewed?
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String MESSAGE_READ = "read";
+
+        /**
+         * Message format (3GPP or 3GPP2).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_FORMAT = "format";
+
+        /**
+         * Message priority (including emergency).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String MESSAGE_PRIORITY = "priority";
+
+        /**
+         * ETWS warning type (ETWS alerts only).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ETWS_WARNING_TYPE = "etws_warning_type";
+
+        /**
+         * CMAS message class (CMAS alerts only).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
+
+        /**
+         * CMAS category (CMAS alerts only).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CMAS_CATEGORY = "cmas_category";
+
+        /**
+         * CMAS response type (CMAS alerts only).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CMAS_RESPONSE_TYPE = "cmas_response_type";
+
+        /**
+         * CMAS severity (CMAS alerts only).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CMAS_SEVERITY = "cmas_severity";
+
+        /**
+         * CMAS urgency (CMAS alerts only).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CMAS_URGENCY = "cmas_urgency";
+
+        /**
+         * CMAS certainty (CMAS alerts only).
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CMAS_CERTAINTY = "cmas_certainty";
+
+        /** The default sort order for this table. */
+        public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC";
+
+        /**
+         * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+         */
+        public static final String[] QUERY_COLUMNS = {
+                _ID,
+                GEOGRAPHICAL_SCOPE,
+                PLMN,
+                LAC,
+                CID,
+                SERIAL_NUMBER,
+                SERVICE_CATEGORY,
+                LANGUAGE_CODE,
+                MESSAGE_BODY,
+                DELIVERY_TIME,
+                MESSAGE_READ,
+                MESSAGE_FORMAT,
+                MESSAGE_PRIORITY,
+                ETWS_WARNING_TYPE,
+                CMAS_MESSAGE_CLASS,
+                CMAS_CATEGORY,
+                CMAS_RESPONSE_TYPE,
+                CMAS_SEVERITY,
+                CMAS_URGENCY,
+                CMAS_CERTAINTY
+        };
+    }
+
+    /**
+     * Constants for interfacing with the ServiceStateProvider and the different fields of the
+     * {@link ServiceState} class accessible through the provider.
+     */
+    public static final class ServiceStateTable {
+
+        /**
+         * Not instantiable.
+         * @hide
+         */
+        private ServiceStateTable() {}
+
+        /**
+         * The authority string for the ServiceStateProvider
+         */
+        public static final String AUTHORITY = "service-state";
+
+        /**
+         * The {@code content://} style URL for the ServiceStateProvider
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://service-state/");
+
+        /**
+         * Generates a content {@link Uri} used to receive updates on a specific field in the
+         * ServiceState provider.
+         * <p>
+         * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the
+         * {@link ServiceState} while your app is running.  You can also use a {@link JobService} to
+         * ensure your app is notified of changes to the {@link Uri} even when it is not running.
+         * Note, however, that using a {@link JobService} does not guarantee timely delivery of
+         * updates to the {@link Uri}.
+         *
+         * @param subscriptionId the subscriptionId to receive updates on
+         * @param field the ServiceState field to receive updates on
+         * @return the Uri used to observe {@link ServiceState} changes
+         */
+        public static Uri getUriForSubscriptionIdAndField(int subscriptionId, String field) {
+            return CONTENT_URI.buildUpon().appendEncodedPath(String.valueOf(subscriptionId))
+                    .appendEncodedPath(field).build();
+        }
+
+        /**
+         * Generates a content {@link Uri} used to receive updates on every field in the
+         * ServiceState provider.
+         * <p>
+         * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the
+         * {@link ServiceState} while your app is running.  You can also use a {@link JobService} to
+         * ensure your app is notified of changes to the {@link Uri} even when it is not running.
+         * Note, however, that using a {@link JobService} does not guarantee timely delivery of
+         * updates to the {@link Uri}.
+         *
+         * @param subscriptionId the subscriptionId to receive updates on
+         * @return the Uri used to observe {@link ServiceState} changes
+         */
+        public static Uri getUriForSubscriptionId(int subscriptionId) {
+            return CONTENT_URI.buildUpon()
+                    .appendEncodedPath(String.valueOf(subscriptionId)).build();
+        }
+
+        /**
+         * Used to insert a ServiceState into the ServiceStateProvider as a ContentValues instance.
+         *
+         * @param state the ServiceState to convert into ContentValues
+         * @return the convertedContentValues instance
+         * @hide
+         */
+        public static ContentValues getContentValuesForServiceState(ServiceState state) {
+            ContentValues values = new ContentValues();
+            values.put(VOICE_REG_STATE, state.getVoiceRegState());
+            values.put(DATA_REG_STATE, state.getDataRegState());
+            values.put(VOICE_ROAMING_TYPE, state.getVoiceRoamingType());
+            values.put(DATA_ROAMING_TYPE, state.getDataRoamingType());
+            values.put(VOICE_OPERATOR_ALPHA_LONG, state.getVoiceOperatorAlphaLong());
+            values.put(VOICE_OPERATOR_ALPHA_SHORT, state.getVoiceOperatorAlphaShort());
+            values.put(VOICE_OPERATOR_NUMERIC, state.getVoiceOperatorNumeric());
+            values.put(DATA_OPERATOR_ALPHA_LONG, state.getDataOperatorAlphaLong());
+            values.put(DATA_OPERATOR_ALPHA_SHORT, state.getDataOperatorAlphaShort());
+            values.put(DATA_OPERATOR_NUMERIC, state.getDataOperatorNumeric());
+            values.put(IS_MANUAL_NETWORK_SELECTION, state.getIsManualSelection());
+            values.put(RIL_VOICE_RADIO_TECHNOLOGY, state.getRilVoiceRadioTechnology());
+            values.put(RIL_DATA_RADIO_TECHNOLOGY, state.getRilDataRadioTechnology());
+            values.put(CSS_INDICATOR, state.getCssIndicator());
+            values.put(NETWORK_ID, state.getNetworkId());
+            values.put(SYSTEM_ID, state.getSystemId());
+            values.put(CDMA_ROAMING_INDICATOR, state.getCdmaRoamingIndicator());
+            values.put(CDMA_DEFAULT_ROAMING_INDICATOR, state.getCdmaDefaultRoamingIndicator());
+            values.put(CDMA_ERI_ICON_INDEX, state.getCdmaEriIconIndex());
+            values.put(CDMA_ERI_ICON_MODE, state.getCdmaEriIconMode());
+            values.put(IS_EMERGENCY_ONLY, state.isEmergencyOnly());
+            values.put(IS_DATA_ROAMING_FROM_REGISTRATION, state.getDataRoamingFromRegistration());
+            values.put(IS_USING_CARRIER_AGGREGATION, state.isUsingCarrierAggregation());
+            return values;
+        }
+
+        /**
+         * An integer value indicating the current voice service state.
+         * <p>
+         * Valid values: {@link ServiceState#STATE_IN_SERVICE},
+         * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
+         * {@link ServiceState#STATE_POWER_OFF}.
+         * <p>
+         * This is the same as {@link ServiceState#getState()}.
+         */
+        public static final String VOICE_REG_STATE = "voice_reg_state";
+
+        /**
+         * An integer value indicating the current data service state.
+         * <p>
+         * Valid values: {@link ServiceState#STATE_IN_SERVICE},
+         * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
+         * {@link ServiceState#STATE_POWER_OFF}.
+         * <p>
+         * This is the same as {@link ServiceState#getDataRegState()}.
+         * @hide
+         */
+        public static final String DATA_REG_STATE = "data_reg_state";
+
+        /**
+         * An integer value indicating the current voice roaming type.
+         * <p>
+         * This is the same as {@link ServiceState#getVoiceRoamingType()}.
+         * @hide
+         */
+        public static final String VOICE_ROAMING_TYPE = "voice_roaming_type";
+
+        /**
+         * An integer value indicating the current data roaming type.
+         * <p>
+         * This is the same as {@link ServiceState#getDataRoamingType()}.
+         * @hide
+         */
+        public static final String DATA_ROAMING_TYPE = "data_roaming_type";
+
+        /**
+         * The current registered voice network operator name in long alphanumeric format.
+         * <p>
+         * This is the same as {@link ServiceState#getVoiceOperatorAlphaLong()}.
+         * @hide
+         */
+        public static final String VOICE_OPERATOR_ALPHA_LONG = "voice_operator_alpha_long";
+
+        /**
+         * The current registered operator name in short alphanumeric format.
+         * <p>
+         * In GSM/UMTS, short format can be up to 8 characters long. The current registered voice
+         * network operator name in long alphanumeric format.
+         * <p>
+         * This is the same as {@link ServiceState#getVoiceOperatorAlphaShort()}.
+         * @hide
+         */
+        public static final String VOICE_OPERATOR_ALPHA_SHORT = "voice_operator_alpha_short";
+
+
+        /**
+         * The current registered operator numeric id.
+         * <p>
+         * In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit
+         * network code.
+         * <p>
+         * This is the same as {@link ServiceState#getOperatorNumeric()}.
+         */
+        public static final String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
+
+        /**
+         * The current registered data network operator name in long alphanumeric format.
+         * <p>
+         * This is the same as {@link ServiceState#getDataOperatorAlphaLong()}.
+         * @hide
+         */
+        public static final String DATA_OPERATOR_ALPHA_LONG = "data_operator_alpha_long";
+
+        /**
+         * The current registered data network operator name in short alphanumeric format.
+         * <p>
+         * This is the same as {@link ServiceState#getDataOperatorAlphaShort()}.
+         * @hide
+         */
+        public static final String DATA_OPERATOR_ALPHA_SHORT = "data_operator_alpha_short";
+
+        /**
+         * The current registered data network operator numeric id.
+         * <p>
+         * This is the same as {@link ServiceState#getDataOperatorNumeric()}.
+         * @hide
+         */
+        public static final String DATA_OPERATOR_NUMERIC = "data_operator_numeric";
+
+        /**
+         * The current network selection mode.
+         * <p>
+         * This is the same as {@link ServiceState#getIsManualSelection()}.
+         */
+        public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
+
+        /**
+         * This is the same as {@link ServiceState#getRilVoiceRadioTechnology()}.
+         * @hide
+         */
+        public static final String RIL_VOICE_RADIO_TECHNOLOGY = "ril_voice_radio_technology";
+
+        /**
+         * This is the same as {@link ServiceState#getRilDataRadioTechnology()}.
+         * @hide
+         */
+        public static final String RIL_DATA_RADIO_TECHNOLOGY = "ril_data_radio_technology";
+
+        /**
+         * This is the same as {@link ServiceState#getCssIndicator()}.
+         * @hide
+         */
+        public static final String CSS_INDICATOR = "css_indicator";
+
+        /**
+         * This is the same as {@link ServiceState#getNetworkId()}.
+         * @hide
+         */
+        public static final String NETWORK_ID = "network_id";
+
+        /**
+         * This is the same as {@link ServiceState#getSystemId()}.
+         * @hide
+         */
+        public static final String SYSTEM_ID = "system_id";
+
+        /**
+         * This is the same as {@link ServiceState#getCdmaRoamingIndicator()}.
+         * @hide
+         */
+        public static final String CDMA_ROAMING_INDICATOR = "cdma_roaming_indicator";
+
+        /**
+         * This is the same as {@link ServiceState#getCdmaDefaultRoamingIndicator()}.
+         * @hide
+         */
+        public static final String CDMA_DEFAULT_ROAMING_INDICATOR =
+                "cdma_default_roaming_indicator";
+
+        /**
+         * This is the same as {@link ServiceState#getCdmaEriIconIndex()}.
+         * @hide
+         */
+        public static final String CDMA_ERI_ICON_INDEX = "cdma_eri_icon_index";
+
+        /**
+         * This is the same as {@link ServiceState#getCdmaEriIconMode()}.
+         * @hide
+         */
+        public static final String CDMA_ERI_ICON_MODE = "cdma_eri_icon_mode";
+
+        /**
+         * This is the same as {@link ServiceState#isEmergencyOnly()}.
+         * @hide
+         */
+        public static final String IS_EMERGENCY_ONLY = "is_emergency_only";
+
+        /**
+         * This is the same as {@link ServiceState#getDataRoamingFromRegistration()}.
+         * @hide
+         */
+        public static final String IS_DATA_ROAMING_FROM_REGISTRATION =
+                "is_data_roaming_from_registration";
+
+        /**
+         * This is the same as {@link ServiceState#isUsingCarrierAggregation()}.
+         * @hide
+         */
+        public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation";
+    }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f3c72af..5c718c7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16,24 +16,37 @@
 
 package android.telephony;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.WorkerThread;
+import android.annotation.SystemApi;
 import android.app.ActivityThread;
+import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.Uri;
 import android.os.BatteryStats;
-import android.os.ResultReceiver;
+import android.os.Binder;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 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.PersistableBundle;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.service.carrier.CarrierIdentifier;
@@ -61,6 +74,7 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -118,9 +132,24 @@
         static final int NEVER_USE = 2;
     }
 
+    /** The otaspMode passed to PhoneStateListener#onOtaspChanged */
+    /** @hide */
+    static public final int OTASP_UNINITIALIZED = 0;
+    /** @hide */
+    static public final int OTASP_UNKNOWN = 1;
+    /** @hide */
+    static public final int OTASP_NEEDED = 2;
+    /** @hide */
+    static public final int OTASP_NOT_NEEDED = 3;
+    /* OtaUtil has conflict enum 4: OtaUtils.OTASP_FAILURE_SPC_RETRIES */
+    /** @hide */
+    static public final int OTASP_SIM_UNPROVISIONED = 5;
+
+
     private final Context mContext;
     private final int mSubId;
     private SubscriptionManager mSubscriptionManager;
+    private TelephonyScanManager mTelephonyScanManager;
 
     private static String multiSimConfig =
             SystemProperties.get(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG);
@@ -838,6 +867,29 @@
      */
     public static final String VVM_TYPE_CVVM = "vvm_type_cvvm";
 
+    /**
+     * @hide
+     */
+    public static final String USSD_RESPONSE = "USSD_RESPONSE";
+
+    /**
+     * USSD return code success.
+     * @hide
+     */
+    public static final int USSD_RETURN_SUCCESS = 100;
+
+    /**
+     * USSD return code for failure case.
+     * @hide
+     */
+    public static final int USSD_RETURN_FAILURE = -1;
+
+    /**
+     * USSD return code for failure case.
+     * @hide
+     */
+    public static final int USSD_ERROR_SERVICE_UNAVAIL = -2;
+
     //
     //
     // Device Info
@@ -853,7 +905,7 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
     public String getDeviceSoftwareVersion() {
-        return getDeviceSoftwareVersion(getDefaultSim());
+        return getDeviceSoftwareVersion(getSlotIndex());
     }
 
     /**
@@ -939,7 +991,7 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
     public String getImei() {
-        return getImei(getDefaultSim());
+        return getImei(getSlotIndex());
     }
 
     /**
@@ -971,7 +1023,7 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
     public String getMeid() {
-        return getMeid(getDefaultSim());
+        return getMeid(getSlotIndex());
     }
 
     /**
@@ -1001,7 +1053,7 @@
      */
     /** {@hide}*/
     public String getNai() {
-        return getNai(getDefaultSim());
+        return getNai(getSlotIndex());
     }
 
     /**
@@ -1035,14 +1087,15 @@
      * this method will return null. The implementation must not to try add LTE
      * identifiers into the existing cdma/gsm classes.
      *<p>
-     * In the future this call will be deprecated.
-     *<p>
      * @return Current location of the device or null if not available.
      *
      * <p>Requires Permission:
      * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
      * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION}.
+     *
+     * @deprecated use {@link #getAllCellInfo} instead, which returns a superset of this API.
      */
+    @Deprecated
     public CellLocation getCellLocation() {
         try {
             ITelephony telephony = getITelephony();
@@ -1244,7 +1297,7 @@
     }
 
     private int getPhoneTypeFromProperty() {
-        return getPhoneTypeFromProperty(getDefaultPhone());
+        return getPhoneTypeFromProperty(getPhoneId());
     }
 
     /** {@hide} */
@@ -1258,7 +1311,7 @@
     }
 
     private int getPhoneTypeFromNetworkType() {
-        return getPhoneTypeFromNetworkType(getDefaultPhone());
+        return getPhoneTypeFromNetworkType(getPhoneId());
     }
 
     /** {@hide} */
@@ -1441,7 +1494,7 @@
      * on a CDMA network).
      */
     public String getNetworkOperator() {
-        return getNetworkOperatorForPhone(getDefaultPhone());
+        return getNetworkOperatorForPhone(getPhoneId());
     }
 
     /**
@@ -1477,18 +1530,23 @@
 
 
     /**
-     * Returns the network specifier of the subscription ID pinned to the TelephonyManager.
+     * Returns the network specifier of the subscription ID pinned to the TelephonyManager. The
+     * network specifier is used by {@link
+     * android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to create a {@link
+     * android.net.NetworkRequest} that connects through the subscription.
      *
      * @see android.net.NetworkRequest.Builder#setNetworkSpecifier(String)
      * @see #createForSubscriptionId(int)
      * @see #createForPhoneAccountHandle(PhoneAccountHandle)
      */
     public String getNetworkSpecifier() {
-        return String.valueOf(mSubId);
+        return String.valueOf(getSubId());
     }
 
     /**
-     * Returns the carrier config of the subscription ID pinned to the TelephonyManager.
+     * Returns the carrier config of the subscription ID pinned to the TelephonyManager. If an
+     * invalid subscription ID is pinned to the TelephonyManager, the returned config will contain
+     * default values.
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
      * READ_PHONE_STATE}
@@ -1497,10 +1555,11 @@
      * @see #createForSubscriptionId(int)
      * @see #createForPhoneAccountHandle(PhoneAccountHandle)
      */
+    @WorkerThread
     public PersistableBundle getCarrierConfig() {
         CarrierConfigManager carrierConfigManager = mContext
                 .getSystemService(CarrierConfigManager.class);
-        return carrierConfigManager.getConfigForSubId(mSubId);
+        return carrierConfigManager.getConfigForSubId(getSubId());
     }
 
     /**
@@ -1537,7 +1596,7 @@
      * on a CDMA network).
      */
     public String getNetworkCountryIso() {
-        return getNetworkCountryIsoForPhone(getDefaultPhone());
+        return getNetworkCountryIsoForPhone(getPhoneId());
     }
 
     /**
@@ -1683,6 +1742,9 @@
      * Returns a constant indicating the radio technology (network type)
      * currently in use on the device for data transmission.
      *
+     * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+     * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+     *
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -1707,7 +1769,7 @@
      * @see #NETWORK_TYPE_HSPAP
      */
     public int getDataNetworkType() {
-        return getDataNetworkType(getSubId());
+        return getDataNetworkType(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
     }
 
     /**
@@ -1931,7 +1993,7 @@
      * @return true if a ICC card is present
      */
     public boolean hasIccCard() {
-        return hasIccCard(getDefaultSim());
+        return hasIccCard(getSlotIndex());
     }
 
     /**
@@ -1972,7 +2034,7 @@
      * @see #SIM_STATE_CARD_RESTRICTED
      */
     public int getSimState() {
-        int slotIndex = getDefaultSim();
+        int slotIndex = getSlotIndex();
         // slotIndex may be invalid due to sim being absent. In that case query all slots to get
         // sim state
         if (slotIndex < 0) {
@@ -2101,7 +2163,7 @@
      * @see #getSimState
      */
     public String getSimOperatorName() {
-        return getSimOperatorNameForPhone(getDefaultPhone());
+        return getSimOperatorNameForPhone(getPhoneId());
     }
 
     /**
@@ -2133,7 +2195,7 @@
      * Returns the ISO country code equivalent for the SIM provider's country code.
      */
     public String getSimCountryIso() {
-        return getSimCountryIsoForPhone(getDefaultPhone());
+        return getSimCountryIsoForPhone(getPhoneId());
     }
 
     /**
@@ -2681,6 +2743,33 @@
         return false;
     }
 
+
+    /**
+     * Returns the package responsible of processing visual voicemail for the subscription ID pinned
+     * to the TelephonyManager. Returns {@code null} when there is no package responsible for
+     * processing visual voicemail for the subscription.
+     *
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
+     * READ_PHONE_STATE}
+     *
+     * @see #createForSubscriptionId(int)
+     * @see #createForPhoneAccountHandle(PhoneAccountHandle)
+     * @see VisualVoicemailService
+     */
+    @Nullable
+    public String getVisualVoicemailPackageName() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony
+                        .getVisualVoicemailPackageName(mContext.getOpPackageName(), getSubId());
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return null;
+    }
+
     /**
      * Enables the visual voicemail SMS filter for a phone account. When the filter is
      * enabled, Incoming SMS messages matching the OMTP VVM SMS interface will be redirected to the
@@ -2757,18 +2846,17 @@
 
     /**
      * @returns the settings of the visual voicemail SMS filter for a phone account set by the
-     * package, or {@code null} if the filter is disabled.
+     * current active visual voicemail client, or {@code null} if the filter is disabled.
      *
      * <p>Requires the calling app to have READ_PRIVILEGED_PHONE_STATE permission.
      */
     /** @hide */
     @Nullable
-    public VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings(String packageName,
-            int subId) {
+    public VisualVoicemailSmsFilterSettings getActiveVisualVoicemailSmsFilterSettings(int subId) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getSystemVisualVoicemailSmsFilterSettings(packageName, subId);
+                return telephony.getActiveVisualVoicemailSmsFilterSettings(subId);
             }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
@@ -2778,6 +2866,35 @@
     }
 
     /**
+     * Send a visual voicemail SMS. The IPC caller must be the current default dialer.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#SEND_SMS SEND_SMS}
+     *
+     * @param phoneAccountHandle The account to send the SMS with.
+     * @param number The destination number.
+     * @param port The destination port for data SMS, or 0 for text SMS.
+     * @param text The message content. For data sms, it will be encoded as a UTF-8 byte stream.
+     * @param sentIntent The sent intent passed to the {@link SmsManager}
+     *
+     * @see SmsManager#sendDataMessage(String, String, short, byte[], PendingIntent, PendingIntent)
+     * @see SmsManager#sendTextMessage(String, String, String, PendingIntent, PendingIntent)
+     *
+     * @hide
+     */
+    public void sendVisualVoicemailSmsForSubscriber(int subId, String number, int port,
+            String text, PendingIntent sentIntent) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.sendVisualVoicemailSmsForSubscriber(
+                        mContext.getOpPackageName(), subId, number, port, text, sentIntent);
+            }
+        } catch (RemoteException ex) {
+        }
+    }
+
+    /**
      * Initial SIM activation state, unknown. Not set by any carrier apps.
      * @hide
      */
@@ -3501,9 +3618,28 @@
      *
      * @param AID Application id. See ETSI 102.221 and 101.220.
      * @return an IccOpenLogicalChannelResponse object.
+     * @deprecated Replaced by {@link #iccOpenLogicalChannel(String, int)}
      */
+    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID) {
-        return iccOpenLogicalChannel(getSubId(), AID);
+        return iccOpenLogicalChannel(getSubId(), AID, -1);
+    }
+
+    /**
+     * Opens a logical channel to the ICC card.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CCHO command.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *
+     * @param AID Application id. See ETSI 102.221 and 101.220.
+     * @param p2 P2 parameter (described in ISO 7816-4).
+     * @return an IccOpenLogicalChannelResponse object.
+     */
+    public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
+        return iccOpenLogicalChannel(getSubId(), AID, p2);
     }
 
     /**
@@ -3517,14 +3653,15 @@
      *
      * @param subId The subscription to use.
      * @param AID Application id. See ETSI 102.221 and 101.220.
+     * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      * @hide
      */
-    public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID) {
+    public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
-                return telephony.iccOpenLogicalChannel(subId, AID);
+                return telephony.iccOpenLogicalChannel(subId, AID, p2);
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
@@ -3903,35 +4040,67 @@
      * subId is returned. Otherwise, the default subId will be returned.
      */
     private int getSubId() {
-      if (mSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-        return getDefaultSubscription();
+      if (SubscriptionManager.isUsableSubIdValue(mSubId)) {
+        return mSubId;
       }
-      return mSubId;
+      return SubscriptionManager.getDefaultSubscriptionId();
     }
 
     /**
-     * Returns Default subscription.
+     * Return an appropriate subscription ID for any situation.
+     *
+     * If this object has been created with {@link #createForSubscriptionId}, then the provided
+     * subId is returned. Otherwise, the preferred subId which is based on caller's context is
+     * returned.
+     * {@see SubscriptionManager#getDefaultDataSubscriptionId()}
+     * {@see SubscriptionManager#getDefaultVoiceSubscriptionId()}
+     * {@see SubscriptionManager#getDefaultSmsSubscriptionId()}
      */
-    private static int getDefaultSubscription() {
-        return SubscriptionManager.getDefaultSubscriptionId();
+    private int getSubId(int preferredSubId) {
+        if (SubscriptionManager.isUsableSubIdValue(mSubId)) {
+            return mSubId;
+        }
+        return preferredSubId;
     }
 
     /**
-     * Returns Default phone.
+     * Return an appropriate phone ID for any situation.
+     *
+     * If this object has been created with {@link #createForSubscriptionId}, then the phoneId
+     * associated with the provided subId is returned. Otherwise, the default phoneId associated
+     * with the default subId will be returned.
      */
-    private static int getDefaultPhone() {
-        return SubscriptionManager.getPhoneId(SubscriptionManager.getDefaultSubscriptionId());
+    private int getPhoneId() {
+        return SubscriptionManager.getPhoneId(getSubId());
     }
 
     /**
-     *  @return default SIM's slot index. If SIM is not inserted, return default SIM slot index.
+     * Return an appropriate phone ID for any situation.
+     *
+     * If this object has been created with {@link #createForSubscriptionId}, then the phoneId
+     * associated with the provided subId is returned. Otherwise, return the phoneId associated
+     * with the preferred subId based on caller's context.
+     * {@see SubscriptionManager#getDefaultDataSubscriptionId()}
+     * {@see SubscriptionManager#getDefaultVoiceSubscriptionId()}
+     * {@see SubscriptionManager#getDefaultSmsSubscriptionId()}
+     */
+    private int getPhoneId(int preferredSubId) {
+        return SubscriptionManager.getPhoneId(getSubId(preferredSubId));
+    }
+
+    /**
+     * Return an appropriate slot index for any situation.
+     *
+     * if this object has been created with {@link #createForSubscriptionId}, then the slot index
+     * associated with the provided subId is returned. Otherwise, return the slot index associated
+     * with the default subId.
+     * If SIM is not inserted, return default SIM slot index.
      *
      * {@hide}
      */
     @VisibleForTesting
-    public int getDefaultSim() {
-        int slotIndex = SubscriptionManager.getSlotIndex(
-                SubscriptionManager.getDefaultSubscriptionId());
+    public int getSlotIndex() {
+        int slotIndex = SubscriptionManager.getSlotIndex(getSubId());
         if (slotIndex == SubscriptionManager.SIM_NOT_INSERTED) {
             slotIndex = SubscriptionManager.DEFAULT_SIM_SLOT_INDEX;
         }
@@ -4239,7 +4408,7 @@
      * Returns null if the query fails.
      *
      *
-     * <p>Requires that the caller has READ_PRIVILEGED_PHONE_STATE
+     * <p>Requires that the caller has READ_PHONE_STATE
      *
      * @return an array of forbidden PLMNs or null if not available
      */
@@ -4252,7 +4421,7 @@
      * Returns null if the query fails.
      *
      *
-     * <p>Requires that the calling app has READ_PRIVILEGED_PHONE_STATE
+     * <p>Requires that the calling app has READ_PHONE_STATE
      *
      * @param subId subscription ID used for authentication
      * @param appType the icc application type, like {@link #APPTYPE_USIM}
@@ -4410,6 +4579,32 @@
     }
 
     /**
+     * Request a network scan.
+     *
+     * This method is asynchronous, so the network scan results will be returned by callback.
+     * The returned NetworkScan will contain a callback method which can be used to stop the scan.
+     *
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *
+     * @param request Contains all the RAT with bands/channels that need to be scanned.
+     * @param callback Returns network scan results or errors.
+     * @return A NetworkScan obj which contains a callback which can stop the scan.
+     * @hide
+     */
+    public NetworkScan requestNetworkScan(
+            NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) {
+        synchronized (this) {
+            if (mTelephonyScanManager == null) {
+                mTelephonyScanManager = new TelephonyScanManager();
+            }
+        }
+        return mTelephonyScanManager.requestNetworkScan(getSubId(), request, callback);
+    }
+
+    /**
      * Ask the radio to connect to the input network and change selection mode to manual.
      *
      * <p>
@@ -4748,7 +4943,7 @@
     /** @hide */
     @SystemApi
     public List<String> getCarrierPackageNamesForIntent(Intent intent) {
-        return getCarrierPackageNamesForIntentAndPhone(intent, getDefaultPhone());
+        return getCarrierPackageNamesForIntentAndPhone(intent, getPhoneId());
     }
 
     /** @hide */
@@ -4944,6 +5139,80 @@
         return new int[0];
     }
 
+    public static abstract class OnReceiveUssdResponseCallback {
+       /**
+        ** Called when USSD has succeeded.
+        **/
+       public void onReceiveUssdResponse(String request, CharSequence response) {};
+
+       /**
+        ** Called when USSD has failed.
+        **/
+       public void onReceiveUssdResponseFailed(String request, int failureCode) {};
+    }
+
+    /**
+     * Sends an Unstructured Supplementary Service Data (USSD) request to the cellular network and
+     * informs the caller of the response via {@code callback}.
+     * <p>Carriers define USSD codes which can be sent by the user to request information such as
+     * the user's current data balance or minutes balance.
+     * <p>Requires permission:
+     * {@link android.Manifest.permission#CALL_PHONE}
+     * @param ussdRequest the USSD command to be executed.
+     * @param callback called by the framework to inform the caller of the result of executing the
+     *                 USSD request (see {@link OnReceiveUssdResponseCallback}).
+     * @param handler the {@link Handler} to run the request on.
+     */
+    @RequiresPermission(android.Manifest.permission.CALL_PHONE)
+    public void sendUssdRequest(String ussdRequest,
+                                final OnReceiveUssdResponseCallback callback, Handler handler) {
+        checkNotNull(callback, "OnReceiveUssdResponseCallback cannot be null.");
+
+        ResultReceiver wrappedCallback = new ResultReceiver(handler) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle ussdResponse) {
+                Rlog.d(TAG, "USSD:" + resultCode);
+                checkNotNull(ussdResponse, "ussdResponse cannot be null.");
+                UssdResponse response = ussdResponse.getParcelable(USSD_RESPONSE);
+
+                if (resultCode == USSD_RETURN_SUCCESS) {
+                    callback.onReceiveUssdResponse(response.getUssdRequest(),
+                            response.getReturnMessage());
+                } else {
+                    callback.onReceiveUssdResponseFailed(response.getUssdRequest(), resultCode);
+                }
+            }
+        };
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.handleUssdRequest(getSubId(), ussdRequest, wrappedCallback);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#sendUSSDCode", e);
+            UssdResponse response = new UssdResponse(ussdRequest, "");
+            Bundle returnData = new Bundle();
+            returnData.putParcelable(USSD_RESPONSE, response);
+            wrappedCallback.send(USSD_ERROR_SERVICE_UNAVAIL, returnData);
+        }
+    }
+
+   /*
+    * @return true, if the device is currently on a technology (e.g. UMTS or LTE) which can support
+    * voice and data simultaneously. This can change based on location or network condition.
+    */
+    public boolean isConcurrentVoiceAndDataAllowed() {
+        try {
+            ITelephony telephony = getITelephony();
+            return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed(
+                    getSubId()));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isConcurrentVoiceAndDataAllowed", e);
+        }
+        return false;
+    }
+
     /** @hide */
     @SystemApi
     public boolean handlePinMmi(String dialString) {
@@ -5052,9 +5321,10 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
-                return telephony.isDataConnectivityPossible();
+                return telephony.isDataConnectivityPossible(getSubId(SubscriptionManager
+                        .getDefaultDataSubscriptionId()));
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#isDataConnectivityPossible", e);
+            Log.e(TAG, "Error calling ITelephony#isDataAllowed", e);
         }
         return false;
     }
@@ -5074,6 +5344,8 @@
 
     /**
      * Turns mobile data on or off.
+     * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+     * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
      *
      * <p>Requires Permission:
      *     {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
@@ -5084,7 +5356,7 @@
      * @see #hasCarrierPrivileges
      */
     public void setDataEnabled(boolean enable) {
-        setDataEnabled(getSubId(), enable);
+        setDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable);
     }
 
     /** @hide */
@@ -5100,23 +5372,45 @@
         }
     }
 
+
+    /**
+     * @deprecated use {@link #isDataEnabled()} instead.
+     * @hide
+     */
+    @SystemApi
+    @Deprecated
+    public boolean getDataEnabled() {
+        return isDataEnabled();
+    }
+
     /**
      * Returns whether mobile data is enabled or not.
      *
-     * <p>Requires Permission:
-     *     {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
-     *     {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
-     *     calling app has carrier privileges.
+     * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+     * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+     *
+     * <p>Requires one of the following permissions:
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
+     * calling app has carrier privileges.
+     *
+     * <p>Note that this does not take into account any data restrictions that may be present on the
+     * calling app. Such restrictions may be inspected with
+     * {@link ConnectivityManager#getRestrictBackgroundStatus}.
      *
      * @return true if mobile data is enabled.
      *
      * @see #hasCarrierPrivileges
      */
-    public boolean getDataEnabled() {
-        return getDataEnabled(getSubId());
+    @SuppressWarnings("deprecation")
+    public boolean isDataEnabled() {
+        return getDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
     }
 
-    /** @hide */
+    /**
+     * @deprecated use {@link #isDataEnabled(int)} instead.
+     * @hide
+     */
     @SystemApi
     public boolean getDataEnabled(int subId) {
         boolean retVal = false;
@@ -5360,7 +5654,7 @@
     * @hide
     */
     public void setSimOperatorNumeric(String numeric) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setSimOperatorNumericForPhone(phoneId, numeric);
     }
 
@@ -5380,7 +5674,7 @@
      * @hide
      */
     public void setSimOperatorName(String name) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setSimOperatorNameForPhone(phoneId, name);
     }
 
@@ -5400,7 +5694,7 @@
     * @hide
     */
     public void setSimCountryIso(String iso) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setSimCountryIsoForPhone(phoneId, iso);
     }
 
@@ -5420,7 +5714,7 @@
      * @hide
      */
     public void setSimState(String state) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setSimStateForPhone(phoneId, state);
     }
 
@@ -5435,35 +5729,75 @@
     }
 
     /**
-     * Set SIM card power state. Request is equivalent to inserting or removing the card.
+     * Requested state of SIM
      *
-     * @param powerUp True if powering up the SIM, otherwise powering down
+     * CARD_POWER_DOWN
+     * Powers down the SIM. SIM must be up prior.
+     *
+     * CARD_POWER_UP
+     * Powers up the SIM normally. SIM must be down prior.
+     *
+     * CARD_POWER_UP_PASS_THROUGH
+     * Powers up the SIM in PASS_THROUGH mode. SIM must be down prior.
+     * When SIM is powered up in PASS_THOUGH mode, the modem does not send
+     * any command to it (for example SELECT of MF, or TERMINAL CAPABILITY),
+     * and the SIM card is controlled completely by Telephony sending APDUs
+     * directly. The SIM card state will be RIL_CARDSTATE_PRESENT and the
+     * number of card apps will be 0.
+     * No new error code is generated. Emergency calls are supported in the
+     * same way as if the SIM card is absent.
+     * The PASS_THROUGH mode is valid only for the specific card session where it
+     * is activated, and normal behavior occurs at the next SIM initialization,
+     * unless PASS_THROUGH mode is requested again. Hence, the last power-up mode
+     * is NOT persistent across boots. On reboot, SIM will power up normally.
+     */
+    /** @hide */
+    public static final int CARD_POWER_DOWN = 0;
+    /** @hide */
+    public static final int CARD_POWER_UP = 1;
+    /** @hide */
+    public static final int CARD_POWER_UP_PASS_THROUGH = 2;
+
+    /**
+     * Set SIM card power state.
+     *
+     * @param state  State of SIM (power down, power up, pass through)
+     * @see #CARD_POWER_DOWN
+     * @see #CARD_POWER_UP
+     * @see #CARD_POWER_UP_PASS_THROUGH
+     * Callers should monitor for {@link TelephonyIntents#ACTION_SIM_STATE_CHANGED}
+     * broadcasts to determine success or failure and timeout if needed.
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
      * @hide
      **/
-    public void setSimPowerState(boolean powerUp) {
-        setSimPowerStateForSlot(getDefaultSim(), powerUp);
+    public void setSimPowerState(int state) {
+        setSimPowerStateForSlot(getSlotIndex(), state);
     }
 
     /**
-     * Set SIM card power state. Request is equivalent to inserting or removing the card.
+     * Set SIM card power state.
      *
      * @param slotIndex SIM slot id
-     * @param powerUp True if powering up the SIM, otherwise powering down
+     * @param state  State of SIM (power down, power up, pass through)
+     * @see #CARD_POWER_DOWN
+     * @see #CARD_POWER_UP
+     * @see #CARD_POWER_UP_PASS_THROUGH
+     * Callers should monitor for {@link TelephonyIntents#ACTION_SIM_STATE_CHANGED}
+     * broadcasts to determine success or failure and timeout if needed.
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
      * @hide
      **/
-    public void setSimPowerStateForSlot(int slotIndex, boolean powerUp) {
+    public void setSimPowerStateForSlot(int slotIndex, int state) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.setSimPowerStateForSlot(slotIndex, powerUp);
+                telephony.setSimPowerStateForSlot(slotIndex, state);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#setSimPowerStateForSlot", e);
@@ -5479,7 +5813,7 @@
      * @hide
      */
     public void setBasebandVersion(String version) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setBasebandVersionForPhone(phoneId, version);
     }
 
@@ -5506,7 +5840,7 @@
      * @hide
      */
     public void setPhoneType(int type) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setPhoneType(phoneId, type);
     }
 
@@ -5534,7 +5868,7 @@
      * @hide
      */
     public String getOtaSpNumberSchema(String defaultValue) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         return getOtaSpNumberSchemaForPhone(phoneId, defaultValue);
     }
 
@@ -5565,7 +5899,7 @@
      * @hide
      */
     public boolean getSmsReceiveCapable(boolean defaultValue) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         return getSmsReceiveCapableForPhone(phoneId, defaultValue);
     }
 
@@ -5596,7 +5930,7 @@
      * @hide
      */
     public boolean getSmsSendCapable(boolean defaultValue) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         return getSmsSendCapableForPhone(phoneId, defaultValue);
     }
 
@@ -5624,7 +5958,7 @@
      * @hide
      */
     public void setNetworkOperatorName(String name) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setNetworkOperatorNameForPhone(phoneId, name);
     }
 
@@ -5646,7 +5980,7 @@
      * @hide
      */
     public void setNetworkOperatorNumeric(String numeric) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setNetworkOperatorNumericForPhone(phoneId, numeric);
     }
 
@@ -5666,7 +6000,7 @@
      * @hide
      */
     public void setNetworkRoaming(boolean isRoaming) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setNetworkRoamingForPhone(phoneId, isRoaming);
     }
 
@@ -5690,7 +6024,7 @@
      * @hide
      */
     public void setNetworkCountryIso(String iso) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId();
         setNetworkCountryIsoForPhone(phoneId, iso);
     }
 
@@ -5710,11 +6044,15 @@
 
     /**
      * Set the network type currently in use on the device for data transmission.
+     *
+     * If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * phoneId associated with the given subId. Otherwise, applies to the phoneId associated with
+     * {@link SubscriptionManager#getDefaultDataSubscriptionId()}
      * @param type the network type currently in use on the device for data transmission
      * @hide
      */
     public void setDataNetworkType(int type) {
-        int phoneId = getDefaultPhone();
+        int phoneId = getPhoneId(SubscriptionManager.getDefaultDataSubscriptionId());
         setDataNetworkTypeForPhone(phoneId, type);
     }
 
@@ -5886,7 +6224,7 @@
      * @hide
      */
     public String getAidForAppType(int appType) {
-        return getAidForAppType(getDefaultSubscription(), appType);
+        return getAidForAppType(getSubId(), appType);
     }
 
     /**
@@ -5920,7 +6258,7 @@
      * @hide
      */
     public String getEsn() {
-        return getEsn(getDefaultSubscription());
+        return getEsn(getSubId());
     }
 
     /**
@@ -5953,7 +6291,7 @@
      * @hide
      */
     public String getCdmaPrlVersion() {
-        return getCdmaPrlVersion(getDefaultSubscription());
+        return getCdmaPrlVersion(getSubId());
     }
 
     /**
@@ -6051,7 +6389,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
         } catch (NullPointerException e) {
-            Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
+            Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
         }
         return new ArrayList<CarrierIdentifier>(0);
     }
@@ -6149,5 +6487,54 @@
 
         return null;
     }
+
+    /**
+     * Check if phone is in emergency callback mode
+     * @return true if phone is in emergency callback mode
+     * @hide
+     */
+    public boolean getEmergencyCallbackMode() {
+        return getEmergencyCallbackMode(getSubId());
+    }
+
+    /**
+     * Check if phone is in emergency callback mode
+     * @return true if phone is in emergency callback mode
+     * @param subId the subscription ID that this action applies to.
+     * @hide
+     */
+    public boolean getEmergencyCallbackMode(int subId) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) {
+                return false;
+            }
+            return telephony.getEmergencyCallbackMode(subId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getEmergencyCallbackMode", e);
+        }
+        return false;
+    }
+
+    /**
+     * Get the most recently available signal strength information.
+     *
+     * Get the most recent SignalStrength information reported by the modem. Due
+     * to power saving this information may not always be current.
+     * @return the most recent cached signal strength info from the modem
+     * @hide
+     */
+    @Nullable
+    public SignalStrength getSignalStrength() {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getSignalStrength(getSubId());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getSignalStrength", e);
+        }
+        return null;
+    }
 }
 
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
new file mode 100644
index 0000000..c905d3a
--- /dev/null
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -0,0 +1,186 @@
+/*
+ * 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;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+import java.util.List;
+
+import com.android.internal.telephony.ITelephony;
+
+/**
+ * Manages the radio access network scan requests and callbacks.
+ * @hide
+ */
+public final class TelephonyScanManager {
+
+    private static final String TAG = "TelephonyScanManager";
+
+    /** @hide */
+    public static final int CALLBACK_SCAN_RESULTS = 1;
+    /** @hide */
+    public static final int CALLBACK_SCAN_ERROR = 2;
+    /** @hide */
+    public static final int CALLBACK_SCAN_COMPLETE = 3;
+
+    /**
+     * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should
+     * implement and provide this callback so that the scan results or errors can be returned.
+     */
+    public static abstract class NetworkScanCallback {
+        /** Returns the scan results to the user, this callback will be called multiple times. */
+        public void onResults(List<CellInfo> results) {}
+
+        /**
+         * Informs the user that the scan has stopped.
+         *
+         * This callback will be called when the scan is finished or cancelled by the user.
+         * The related NetworkScanRequest will be deleted after this callback.
+         */
+        public void onComplete() {}
+
+        /**
+         * Informs the user that there is some error about the scan.
+         *
+         * This callback will be called whenever there is any error about the scan, but the scan
+         * won't stop unless the onComplete() callback is called.
+         */
+        public void onError(int error) {}
+    }
+
+    private static class NetworkScanInfo {
+        private final NetworkScanRequest mRequest;
+        private final NetworkScanCallback mCallback;
+
+        NetworkScanInfo(NetworkScanRequest request, NetworkScanCallback callback) {
+            mRequest = request;
+            mCallback = callback;
+        }
+    }
+
+    private final Looper mLooper;
+    private final Messenger mMessenger;
+    private SparseArray<NetworkScanInfo> mScanInfo = new SparseArray<NetworkScanInfo>();
+
+    public TelephonyScanManager() {
+        HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        mLooper = thread.getLooper();
+        mMessenger = new Messenger(new Handler(mLooper) {
+            @Override
+            public void handleMessage(Message message) {
+                checkNotNull(message, "message cannot be null");
+                NetworkScanInfo nsi;
+                synchronized (mScanInfo) {
+                    nsi = mScanInfo.get(message.arg2);
+                }
+                if (nsi == null) {
+                    throw new RuntimeException(
+                        "Failed to find NetworkScanInfo with id " + message.arg2);
+                }
+                NetworkScanCallback callback = nsi.mCallback;
+                if (callback == null) {
+                    throw new RuntimeException(
+                        "Failed to find NetworkScanCallback with id " + message.arg2);
+                }
+
+                switch (message.what) {
+                    case CALLBACK_SCAN_RESULTS:
+                        try {
+                            callback.onResults((List<CellInfo>) message.obj);
+                        } catch (Exception e) {
+                            Rlog.e(TAG, "Exception in networkscan callback onResults", e);
+                        }
+                        break;
+                    case CALLBACK_SCAN_ERROR:
+                        try {
+                            callback.onError(message.arg1);
+                        } catch (Exception e) {
+                            Rlog.e(TAG, "Exception in networkscan callback onError", e);
+                        }
+                        break;
+                    case CALLBACK_SCAN_COMPLETE:
+                        try {
+                            callback.onComplete();
+                            mScanInfo.remove(message.arg2);
+                        } catch (Exception e) {
+                            Rlog.e(TAG, "Exception in networkscan callback onComplete", e);
+                        }
+                        break;
+                    default:
+                        Rlog.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
+                        break;
+                }
+            }
+        });
+    }
+
+    /**
+     * Request a network scan.
+     *
+     * This method is asynchronous, so the network scan results will be returned by callback.
+     * The returned NetworkScan will contain a callback method which can be used to stop the scan.
+     *
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *
+     * @param request Contains all the RAT with bands/channels that need to be scanned.
+     * @param callback Returns network scan results or errors.
+     * @return A NetworkScan obj which contains a callback which can stop the scan.
+     * @hide
+     */
+    public NetworkScan requestNetworkScan(int subId,
+            NetworkScanRequest request, NetworkScanCallback callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int scanId = telephony.requestNetworkScan(subId, request, mMessenger, new Binder());
+                saveScanInfo(scanId, request, callback);
+                return new NetworkScan(scanId, subId);
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "requestNetworkScan RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "requestNetworkScan NPE", ex);
+        }
+        return null;
+    }
+
+    private void saveScanInfo(int id, NetworkScanRequest request, NetworkScanCallback callback) {
+        synchronized (mScanInfo) {
+            mScanInfo.put(id, new NetworkScanInfo(request, callback));
+        }
+    }
+
+    private ITelephony getITelephony() {
+        return ITelephony.Stub.asInterface(
+            ServiceManager.getService(Context.TELEPHONY_SERVICE));
+    }
+}
diff --git a/telephony/java/android/telephony/UssdResponse.aidl b/telephony/java/android/telephony/UssdResponse.aidl
new file mode 100644
index 0000000..add28a0
--- /dev/null
+++ b/telephony/java/android/telephony/UssdResponse.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony;
+
+parcelable UssdResponse;
diff --git a/telephony/java/android/telephony/UssdResponse.java b/telephony/java/android/telephony/UssdResponse.java
new file mode 100644
index 0000000..5df681d
--- /dev/null
+++ b/telephony/java/android/telephony/UssdResponse.java
@@ -0,0 +1,80 @@
+/*
+ * 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.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Represents the Ussd response, including
+ * the message and the return code.
+ * @hide
+ */
+public final class UssdResponse implements Parcelable {
+    private CharSequence mReturnMessage;
+    private String mUssdRequest;
+
+
+    /**
+     * Implement the Parcelable interface
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mUssdRequest);
+        TextUtils.writeToParcel(mReturnMessage, dest, 0);
+    }
+
+    public String getUssdRequest() {
+        return mUssdRequest;
+    }
+
+    public CharSequence getReturnMessage() {
+        return mReturnMessage;
+    }
+
+    /**
+     * Implement the Parcelable interface
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * * Initialize the object from the request and return message.
+     */
+    public UssdResponse(String ussdRequest, CharSequence returnMessage) {
+        mUssdRequest = ussdRequest;
+        mReturnMessage = returnMessage;
+    }
+
+    public static final Parcelable.Creator<UssdResponse> CREATOR = new Creator<UssdResponse>() {
+
+        @Override
+        public UssdResponse createFromParcel(Parcel in) {
+            String request = in.readString();
+            CharSequence message = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            return new UssdResponse(request, message);
+        }
+
+        @Override
+        public UssdResponse[] newArray(int size) {
+            return new UssdResponse[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/VisualVoicemailService.java b/telephony/java/android/telephony/VisualVoicemailService.java
new file mode 100644
index 0000000..84833e3
--- /dev/null
+++ b/telephony/java/android/telephony/VisualVoicemailService.java
@@ -0,0 +1,281 @@
+/*
+ * 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;
+
+import android.annotation.MainThread;
+import android.annotation.SdkConstant;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+/**
+ * This service is implemented by dialer apps that wishes to handle OMTP or similar visual
+ * voicemails. Telephony binds to this service when the cell service is first connected, a visual
+ * voicemail SMS has been received, or when a SIM has been removed. Telephony will only bind to the
+ * default dialer for such events (See {@link TelecomManager#getDefaultDialerPackage()}). The
+ * {@link android.service.carrier.CarrierMessagingService} precedes the VisualVoicemailService in
+ * the SMS filtering chain and may intercept the visual voicemail SMS before it reaches this
+ * service.
+ * <p>
+ * Below is an example manifest registration for a {@code VisualVoicemailService}.
+ * <pre>
+ * {@code
+ * <service android:name="your.package.YourVisualVoicemailServiceImplementation"
+ *          android:permission="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE">
+ *      <intent-filter>
+ *          <action android:name="android.telephony.VisualVoicemailService"/>
+ *      </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ */
+public abstract class VisualVoicemailService extends Service {
+
+    private static final String TAG = "VvmService";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.telephony.VisualVoicemailService";
+
+    /**
+     * @hide
+     */
+    public static final int MSG_ON_CELL_SERVICE_CONNECTED = 1;
+    /**
+     * @hide
+     */
+    public static final int MSG_ON_SMS_RECEIVED = 2;
+    /**
+     * @hide
+     */
+    public static final int MSG_ON_SIM_REMOVED = 3;
+    /**
+     * @hide
+     */
+    public static final int MSG_TASK_ENDED = 4;
+    /**
+     * @hide
+     */
+    public static final int MSG_TASK_STOPPED = 5;
+
+    /**
+     * @hide
+     */
+    public static final String DATA_PHONE_ACCOUNT_HANDLE = "data_phone_account_handle";
+    /**
+     * @hide
+     */
+    public static final String DATA_SMS = "data_sms";
+
+    /**
+     * Represents a visual voicemail event which needs to be handled. While the task is being
+     * processed telephony will hold a wakelock for the VisualVoicemailService. The service can
+     * unblock the main thread and pass the task to a worker thread. Once the task is finished,
+     * {@link VisualVoicemailTask#finish()} should be called to signal telephony to release the
+     * resources. Telephony will call {@link VisualVoicemailService#onStopped(VisualVoicemailTask)}
+     * when the task is going to be terminated before completion.
+     *
+     * @see #onCellServiceConnected(VisualVoicemailTask, PhoneAccountHandle)
+     * @see #onSmsReceived(VisualVoicemailTask, VisualVoicemailSms)
+     * @see #onSimRemoved(VisualVoicemailTask, PhoneAccountHandle)
+     * @see #onStopped(VisualVoicemailTask)
+     */
+    public static class VisualVoicemailTask {
+
+        private final int mTaskId;
+        private final Messenger mReplyTo;
+
+        private VisualVoicemailTask(Messenger replyTo, int taskId) {
+            mTaskId = taskId;
+            mReplyTo = replyTo;
+        }
+
+        /**
+         * Call to signal telephony the task has completed. Must be called for every task.
+         */
+        public final void finish() {
+            Message message = Message.obtain();
+            try {
+                message.what = MSG_TASK_ENDED;
+                message.arg1 = mTaskId;
+                mReplyTo.send(message);
+            } catch (RemoteException e) {
+                Log.e(TAG,
+                        "Cannot send MSG_TASK_ENDED, remote handler no longer exist");
+            }
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof VisualVoicemailTask)) {
+                return false;
+            }
+            return mTaskId == ((VisualVoicemailTask) obj).mTaskId;
+        }
+
+        @Override
+        public int hashCode() {
+            return mTaskId;
+        }
+    }
+
+    /**
+     * Handles messages sent by telephony.
+     */
+    private final Messenger mMessenger = new Messenger(new Handler() {
+        @Override
+        public void handleMessage(final Message msg) {
+            final PhoneAccountHandle handle = msg.getData()
+                    .getParcelable(DATA_PHONE_ACCOUNT_HANDLE);
+            VisualVoicemailTask task = new VisualVoicemailTask(msg.replyTo, msg.arg1);
+            switch (msg.what) {
+                case MSG_ON_CELL_SERVICE_CONNECTED:
+                    onCellServiceConnected(task, handle);
+                    break;
+                case MSG_ON_SMS_RECEIVED:
+                    VisualVoicemailSms sms = msg.getData().getParcelable(DATA_SMS);
+                    onSmsReceived(task, sms);
+                    break;
+                case MSG_ON_SIM_REMOVED:
+                    onSimRemoved(task, handle);
+                    break;
+                case MSG_TASK_STOPPED:
+                    onStopped(task);
+                    break;
+                default:
+                    super.handleMessage(msg);
+                    break;
+            }
+        }
+    });
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    /**
+     * Called when the cellular service is connected on a {@link PhoneAccountHandle} for the first
+     * time, or when the carrier config has changed. It will not be called when the signal is lost
+     * then restored.
+     *
+     * @param task The task representing this event. {@link VisualVoicemailTask#finish()} must be
+     * called when the task is completed.
+     * @param phoneAccountHandle The {@link PhoneAccountHandle} triggering this event.
+     */
+    @MainThread
+    public abstract void onCellServiceConnected(VisualVoicemailTask task,
+                                                PhoneAccountHandle phoneAccountHandle);
+
+    /**
+     * Called when a SMS matching the {@link VisualVoicemailSmsFilterSettings} set by
+     * {@link #setSmsFilterSettings(Context, PhoneAccountHandle, VisualVoicemailSmsFilterSettings)}
+     * is received.
+     *
+     * @param task The task representing this event. {@link VisualVoicemailTask#finish()} must be
+     * called when the task is completed.
+     * @param sms The content of the received SMS.
+     */
+    @MainThread
+    public abstract void onSmsReceived(VisualVoicemailTask task,
+                                       VisualVoicemailSms sms);
+
+    /**
+     * Called when a SIM is removed.
+     *
+     * @param task The task representing this event. {@link VisualVoicemailTask#finish()} must be
+     * called when the task is completed.
+     * @param phoneAccountHandle The {@link PhoneAccountHandle} triggering this event.
+     */
+    @MainThread
+    public abstract void onSimRemoved(VisualVoicemailTask task,
+                                      PhoneAccountHandle phoneAccountHandle);
+
+    /**
+     * Called before the system is about to terminate a task. The service should persist any
+     * necessary data and call finish on the task immediately.
+     */
+    @MainThread
+    public abstract void onStopped(VisualVoicemailTask task);
+
+    /**
+     * Set the visual voicemail SMS filter settings for the VisualVoicemailService.
+     * {@link #onSmsReceived(VisualVoicemailTask, VisualVoicemailSms)} will be called when
+     * a SMS matching the settings is received. The caller should have
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} and implements a
+     * VisualVoicemailService.
+     * <p>
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @param phoneAccountHandle The account to apply the settings to.
+     * @param settings The settings for the filter, or {@code null} to disable the filter.
+     */
+    public final static void setSmsFilterSettings(Context context,
+            PhoneAccountHandle phoneAccountHandle,
+            VisualVoicemailSmsFilterSettings settings) {
+        TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+        int subId = getSubId(context, phoneAccountHandle);
+        if (settings == null) {
+            telephonyManager.disableVisualVoicemailSmsFilter(subId);
+        } else {
+            telephonyManager.enableVisualVoicemailSmsFilter(subId, settings);
+        }
+    }
+
+    /**
+     * Send a visual voicemail SMS. The caller must be the current default dialer.
+     * <p>
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#SEND_SMS SEND_SMS}
+     *
+     * @param phoneAccountHandle The account to send the SMS with.
+     * @param number The destination number.
+     * @param port The destination port for data SMS, or 0 for text SMS.
+     * @param text The message content. For data sms, it will be encoded as a UTF-8 byte stream.
+     * @param sentIntent The sent intent passed to the {@link SmsManager}
+     * @see SmsManager#sendDataMessage(String, String, short, byte[], PendingIntent, PendingIntent)
+     * @see SmsManager#sendTextMessage(String, String, String, PendingIntent, PendingIntent)
+     */
+    public final static void sendVisualVoicemailSms(Context context,
+            PhoneAccountHandle phoneAccountHandle, String number,
+            short port, String text, PendingIntent sentIntent) {
+        TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+        telephonyManager.sendVisualVoicemailSmsForSubscriber(getSubId(context, phoneAccountHandle),
+                number, port, text, sentIntent);
+    }
+
+    private static int getSubId(Context context, PhoneAccountHandle phoneAccountHandle) {
+        TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+        TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+        return telephonyManager
+                .getSubIdForPhoneAccount(telecomManager.getPhoneAccount(phoneAccountHandle));
+    }
+
+}
diff --git a/telephony/java/android/telephony/VisualVoicemailSms.java b/telephony/java/android/telephony/VisualVoicemailSms.java
new file mode 100644
index 0000000..1e6ea4b
--- /dev/null
+++ b/telephony/java/android/telephony/VisualVoicemailSms.java
@@ -0,0 +1,149 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.VisualVoicemailService.VisualVoicemailTask;
+
+/**
+ * Represents the content of a visual voicemail SMS. If a incoming SMS matches the {@link
+ * VisualVoicemailSmsFilterSettings} set by the default dialer, {@link
+ * VisualVoicemailService#onSmsReceived(VisualVoicemailTask, VisualVoicemailSms)} will be called.
+ */
+public final class VisualVoicemailSms implements Parcelable {
+
+    private final PhoneAccountHandle mPhoneAccountHandle;
+    @Nullable
+    private final String mPrefix;
+
+    @Nullable
+    private final Bundle mFields;
+
+    private final String mMessageBody;
+
+    VisualVoicemailSms(Builder builder) {
+        mPhoneAccountHandle = builder.mPhoneAccountHandle;
+        mPrefix = builder.mPrefix;
+        mFields = builder.mFields;
+        mMessageBody = builder.mMessageBody;
+    }
+
+    /**
+     * The {@link PhoneAccountHandle} that received the SMS.
+     */
+    public PhoneAccountHandle getPhoneAccountHandle() {
+        return mPhoneAccountHandle;
+    }
+
+    /**
+     * The event type of the SMS or {@code null} if the framework cannot parse the SMS as voicemail
+     * but the carrier pattern indicates it is. Common values are "SYNC" or "STATUS".
+     */
+    public String getPrefix() {
+        return mPrefix;
+    }
+
+    /**
+     * The key-value pairs sent by the SMS, or {@code null} if the framework cannot parse the SMS as
+     * voicemail but the carrier pattern indicates it is. The interpretation of the fields is
+     * carrier dependent.
+     */
+    public Bundle getFields() {
+        return mFields;
+    }
+
+    /**
+     * Raw message body of the received SMS.
+     */
+    public String getMessageBody() {
+        return mMessageBody;
+    }
+
+    /**
+     * Builder for the {@link VisualVoicemailSms}. Internal use only.
+     *
+     * @hide
+     */
+    public static class Builder {
+
+        private PhoneAccountHandle mPhoneAccountHandle;
+        private String mPrefix;
+        private Bundle mFields;
+        private String mMessageBody;
+
+        public VisualVoicemailSms build() {
+            return new VisualVoicemailSms(this);
+        }
+
+        public Builder setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
+            this.mPhoneAccountHandle = phoneAccountHandle;
+            return this;
+        }
+
+        public Builder setPrefix(String prefix) {
+            this.mPrefix = prefix;
+            return this;
+        }
+
+        public Builder setFields(Bundle fields) {
+            this.mFields = fields;
+            return this;
+        }
+
+        public Builder setMessageBody(String messageBody) {
+            this.mMessageBody = messageBody;
+            return this;
+        }
+
+    }
+
+
+    public static final Creator<VisualVoicemailSms> CREATOR =
+            new Creator<VisualVoicemailSms>() {
+                @Override
+                public VisualVoicemailSms createFromParcel(Parcel in) {
+                    return new Builder()
+                            .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null))
+                            .setPrefix(in.readString())
+                            .setFields(in.readBundle())
+                            .setMessageBody(in.readString())
+                            .build();
+                }
+
+                @Override
+                public VisualVoicemailSms[] newArray(int size) {
+                    return new VisualVoicemailSms[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(getPhoneAccountHandle(), flags);
+        dest.writeString(getPrefix());
+        dest.writeBundle(getFields());
+        dest.writeString(getMessageBody());
+    }
+}
diff --git a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
index 5b81027..56a8c62 100644
--- a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
+++ b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
@@ -15,9 +15,12 @@
  */
 package android.telephony;
 
+import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import android.telecom.PhoneAccountHandle;
+import android.telephony.VisualVoicemailService.VisualVoicemailTask;
 import java.util.Collections;
 import java.util.List;
 
@@ -28,42 +31,44 @@
  * <p>[clientPrefix]:[prefix]:([key]=[value];)*
  *
  * <p>will be regarded as a visual voicemail SMS, and removed before reaching the SMS provider. The
- * intent {@link android.provider.VoicemailContract#ACTION_VOICEMAIL_SMS_RECEIVED} will then be sent
- * to the default dialer with the information extracted from the SMS.
+ * {@link VisualVoicemailService} in the current default dialer will be bound and
+ * {@link VisualVoicemailService#onSmsReceived(VisualVoicemailTask, VisualVoicemailSms)}
+ * will called with the information extracted from the SMS.
  *
  * <p>Use {@link android.telephony.VisualVoicemailSmsFilterSettings.Builder} to construct this
  * class.
  *
- * @see android.telephony.TelephonyManager#enableVisualVoicemailSmsFilter
- *
- * @hide
+ * @see VisualVoicemailService#setSmsFilterSettings(Context, PhoneAccountHandle, VisualVoicemailSmsFilterSettings)
  */
-public class VisualVoicemailSmsFilterSettings implements Parcelable {
+public final class VisualVoicemailSmsFilterSettings implements Parcelable {
 
 
     /**
      * The visual voicemail SMS message does not have to be a data SMS, and can be directed to any
      * port.
-     *
-     * @hide
      */
     public static final int DESTINATION_PORT_ANY = -1;
 
     /**
      * The visual voicemail SMS message can be directed to any port, but must be a data SMS.
-     *
-     * @hide
      */
     public static final int DESTINATION_PORT_DATA_SMS = -2;
 
+    /**
+     * @hide
+     */
     public static final String DEFAULT_CLIENT_PREFIX = "//VVM";
+    /**
+     * @hide
+     */
     public static final List<String> DEFAULT_ORIGINATING_NUMBERS = Collections.emptyList();
+    /**
+     * @hide
+     */
     public static final int DEFAULT_DESTINATION_PORT = DESTINATION_PORT_ANY;
 
     /**
      * Builder class for {@link VisualVoicemailSmsFilterSettings} objects.
-     *
-     * @hide
      */
     public static class Builder {
 
@@ -171,4 +176,13 @@
         dest.writeInt(destinationPort);
     }
 
+    @Override
+    public String toString(){
+        return "[VisualVoicemailSmsFilterSettings "
+                + "clientPrefix=" + clientPrefix
+                + ", originatingNumbers=" + originatingNumbers
+                + ", destinationPort=" + destinationPort
+                + "]";
+    }
+
 }
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index f1f683c..511ac38 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -105,10 +105,11 @@
         }
 
         @Override
-        public void removeImsFeature(int slotId, int feature) throws RemoteException {
+        public void removeImsFeature(int slotId, int feature,  IImsFeatureStatusCallback c)
+                throws RemoteException {
             synchronized (mFeatures) {
                 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "removeImsFeature");
-                onRemoveImsFeatureInternal(slotId, feature);
+                onRemoveImsFeatureInternal(slotId, feature, c);
             }
         }
 
@@ -355,7 +356,7 @@
         if (f != null) {
             f.setContext(this);
             f.setSlotId(slotId);
-            f.setImsFeatureStatusCallback(c);
+            f.addImsFeatureStatusCallback(c);
             featureMap.put(featureType, f);
         }
 
@@ -368,7 +369,8 @@
      * defined in {@link ImsFeature}.
      */
     // Be sure to lock on mFeatures before accessing this method
-    private void onRemoveImsFeatureInternal(int slotId, int featureType) {
+    private void onRemoveImsFeatureInternal(int slotId, int featureType,
+            IImsFeatureStatusCallback c) {
         SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
         if (featureMap == null) {
             return;
@@ -379,7 +381,7 @@
             featureMap.remove(featureType);
             featureToRemove.notifyFeatureRemoved(slotId);
             // Remove reference to Binder
-            featureToRemove.setImsFeatureStatusCallback(null);
+            featureToRemove.removeImsFeatureStatusCallback(c);
         }
     }
 
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxy.java b/telephony/java/android/telephony/ims/ImsServiceProxy.java
index 38ea6e6f..a75cd86 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxy.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxy.java
@@ -120,7 +120,7 @@
     public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
             throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature,
                     incomingCallIntent, listener);
         }
@@ -129,7 +129,7 @@
     @Override
     public void endSession(int sessionId) throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
         }
     }
@@ -138,7 +138,7 @@
     public boolean isConnected(int callServiceType, int callType)
             throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
                     callServiceType, callType);
         }
@@ -147,7 +147,7 @@
     @Override
     public boolean isOpened() throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
         }
     }
@@ -156,7 +156,7 @@
     public void addRegistrationListener(IImsRegistrationListener listener)
     throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
                     listener);
         }
@@ -166,7 +166,7 @@
     public void removeRegistrationListener(IImsRegistrationListener listener)
             throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
                     listener);
         }
@@ -176,7 +176,7 @@
     public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
             throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature,
                     sessionId, callServiceType, callType);
         }
@@ -186,7 +186,7 @@
     public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
             IImsCallSessionListener listener) throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature,
                     sessionId, profile, listener);
         }
@@ -196,7 +196,7 @@
     public IImsCallSession getPendingCallSession(int sessionId, String callId)
             throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature,
                     sessionId, callId);
         }
@@ -205,7 +205,7 @@
     @Override
     public IImsUt getUtInterface() throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
         }
     }
@@ -213,7 +213,7 @@
     @Override
     public IImsConfig getConfigInterface() throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
         }
     }
@@ -221,7 +221,7 @@
     @Override
     public void turnOnIms() throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
         }
     }
@@ -229,7 +229,7 @@
     @Override
     public void turnOffIms() throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
         }
     }
@@ -237,7 +237,7 @@
     @Override
     public IImsEcbm getEcbmInterface() throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
         }
     }
@@ -246,7 +246,7 @@
     public void setUiTTYMode(int uiTtyMode, Message onComplete)
             throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
                     onComplete);
         }
@@ -255,7 +255,7 @@
     @Override
     public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
         synchronized (mLock) {
-            checkBinderConnection();
+            checkServiceIsReady();
             return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
                     mSupportedFeature);
         }
@@ -264,7 +264,8 @@
     @Override
     public int getFeatureStatus() {
         synchronized (mLock) {
-            if (mFeatureStatusCached != null) {
+            if (isBinderAlive() && mFeatureStatusCached != null) {
+                Log.i(LOG_TAG, "getFeatureStatus - returning cached: " + mFeatureStatusCached);
                 return mFeatureStatusCached;
             }
         }
@@ -277,6 +278,7 @@
             // Cache only non-null value for feature status.
             mFeatureStatusCached = status;
         }
+        Log.i(LOG_TAG, "getFeatureStatus - returning " + status);
         return status;
     }
 
@@ -301,10 +303,28 @@
         mStatusCallback = c;
     }
 
+    /**
+     * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
+     * method returns false, it doesn't mean that the Binder connection is not available (use
+     * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
+     * at this time.
+     *
+     * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
+     * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_NOT_AVAILABLE}.
+     */
+    public boolean isBinderReady() {
+        return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
+    }
+
     @Override
     public boolean isBinderAlive() {
-        return mIsAvailable && getFeatureStatus() == ImsFeature.STATE_READY && mBinder != null &&
-                mBinder.isBinderAlive();
+        return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
+    }
+
+    protected void checkServiceIsReady() throws RemoteException {
+        if (!isBinderReady()) {
+            throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
+        }
     }
 
     private IImsServiceController getServiceInterface(IBinder b) {
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 988dd58..9d880b7 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -28,7 +28,11 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
 
 /**
  * Base class for all IMS features that are supported by the framework.
@@ -88,7 +92,8 @@
     public static final int STATE_READY = 2;
 
     private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>();
-    private IImsFeatureStatusCallback mStatusCallback;
+    private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap(
+            new WeakHashMap<IImsFeatureStatusCallback, Boolean>());
     private @ImsState int mState = STATE_NOT_AVAILABLE;
     private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
     private Context mContext;
@@ -136,11 +141,29 @@
         }
     }
 
-    // Not final for testing.
-    public void setImsFeatureStatusCallback(IImsFeatureStatusCallback c) {
-        mStatusCallback = c;
-        // If we have just connected, send queued status.
-        notifyFeatureState(mState);
+    public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) {
+        if (c == null) {
+            return;
+        }
+        try {
+            // If we have just connected, send queued status.
+            c.notifyImsFeatureStatus(mState);
+            // Add the callback if the callback completes successfully without a RemoteException.
+            synchronized (mStatusCallbacks) {
+                mStatusCallbacks.add(c);
+            }
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
+        }
+    }
+
+    public void removeImsFeatureStatusCallback(IImsFeatureStatusCallback c) {
+        if (c == null) {
+            return;
+        }
+        synchronized (mStatusCallbacks) {
+            mStatusCallbacks.remove(c);
+        }
     }
 
     /**
@@ -148,13 +171,18 @@
      * @param state
      */
     private void notifyFeatureState(@ImsState int state) {
-        if (mStatusCallback != null) {
-            try {
-                Log.i(LOG_TAG, "notifying ImsFeatureState");
-                mStatusCallback.notifyImsFeatureStatus(state);
-            } catch (RemoteException e) {
-                mStatusCallback = null;
-                Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
+        synchronized (mStatusCallbacks) {
+            for (Iterator<IImsFeatureStatusCallback> iter = mStatusCallbacks.iterator();
+                 iter.hasNext(); ) {
+                IImsFeatureStatusCallback callback = iter.next();
+                try {
+                    Log.i(LOG_TAG, "notifying ImsFeatureState=" + state);
+                    callback.notifyImsFeatureStatus(state);
+                } catch (RemoteException e) {
+                    // remove if the callback is no longer alive.
+                    iter.remove();
+                    Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
+                }
             }
         }
         sendImsServiceIntent(state);
diff --git a/telephony/java/android/telephony/mbms/DownloadCallback.java b/telephony/java/android/telephony/mbms/DownloadCallback.java
new file mode 100644
index 0000000..0c6fec4
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadCallback.java
@@ -0,0 +1,41 @@
+/*
+ * 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/DownloadRequest.aidl b/telephony/java/android/telephony/mbms/DownloadRequest.aidl
new file mode 100755
index 0000000..ece577d
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.aidl
@@ -0,0 +1,19 @@
+/*
+** 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 DownloadRequest;
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
new file mode 100644
index 0000000..dbaf10bb
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.content.Intent;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.URISyntaxException;
+
+/**
+ * A Parcelable class describing a pending Cell-Broadcast download request
+ * @hide
+ */
+public class DownloadRequest implements Parcelable {
+    /** @hide */
+    public static class Builder {
+        private int id;
+        private FileServiceInfo serviceInfo;
+        private Uri source;
+        private Uri dest;
+        private int sub;
+        private String appIntent;
+
+        public Builder setId(int id) {
+            this.id = id;
+            return this;
+        }
+
+        public Builder setServiceInfo(FileServiceInfo serviceInfo) {
+            this.serviceInfo = serviceInfo;
+            return this;
+        }
+
+        public Builder setSource(Uri source) {
+            this.source = source;
+            return this;
+        }
+
+        public Builder setDest(Uri dest) {
+            this.dest = dest;
+            return this;
+        }
+
+        public Builder setSub(int sub) {
+            this.sub = sub;
+            return this;
+        }
+
+        public Builder setAppIntent(Intent intent) {
+            this.appIntent = intent.toUri(0);
+            return this;
+        }
+
+        public DownloadRequest build() {
+            return new DownloadRequest(id, serviceInfo, source, dest, sub, appIntent);
+        }
+    }
+
+    private final int downloadId;
+    private final FileServiceInfo fileServiceInfo;
+    private final Uri sourceUri;
+    private final Uri destinationUri;
+    private final int subId;
+    private final String serializedResultIntentForApp;
+
+    private DownloadRequest(int id, FileServiceInfo serviceInfo,
+            Uri source, Uri dest,
+            int sub, String appIntent) {
+        downloadId = id;
+        fileServiceInfo = serviceInfo;
+        sourceUri = source;
+        destinationUri = dest;
+        subId = sub;
+        serializedResultIntentForApp = appIntent;
+    }
+
+    public static DownloadRequest copy(DownloadRequest other) {
+        return new DownloadRequest(other);
+    }
+
+    private DownloadRequest(DownloadRequest dr) {
+        downloadId = dr.downloadId;
+        fileServiceInfo = dr.fileServiceInfo;
+        sourceUri = dr.sourceUri;
+        destinationUri = dr.destinationUri;
+        subId = dr.subId;
+        serializedResultIntentForApp = dr.serializedResultIntentForApp;
+    }
+
+    private DownloadRequest(Parcel in) {
+        downloadId = in.readInt();
+        fileServiceInfo = in.readParcelable(getClass().getClassLoader());
+        sourceUri = in.readParcelable(getClass().getClassLoader());
+        destinationUri = in.readParcelable(getClass().getClassLoader());
+        subId = in.readInt();
+        serializedResultIntentForApp = in.readString();
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(downloadId);
+        out.writeParcelable(fileServiceInfo, flags);
+        out.writeParcelable(sourceUri, flags);
+        out.writeParcelable(destinationUri, flags);
+        out.writeInt(subId);
+        out.writeString(serializedResultIntentForApp);
+    }
+
+    public int getDownloadId() {
+        return downloadId;
+    }
+
+    public FileServiceInfo getFileServiceInfo() {
+        return fileServiceInfo;
+    }
+
+    public Uri getSourceUri() {
+        return sourceUri;
+    }
+
+    public Uri getDestinationUri() {
+        return destinationUri;
+    }
+
+    public int getSubId() {
+        return subId;
+    }
+
+    public Intent getIntentForApp() {
+        try {
+            return Intent.parseUri(serializedResultIntentForApp, 0);
+        } catch (URISyntaxException e) {
+            return null;
+        }
+    }
+
+    public static final Parcelable.Creator<DownloadRequest> CREATOR =
+            new Parcelable.Creator<DownloadRequest>() {
+        public DownloadRequest createFromParcel(Parcel in) {
+            return new DownloadRequest(in);
+        }
+        public DownloadRequest[] newArray(int size) {
+            return new DownloadRequest[size];
+        }
+    };
+
+}
diff --git a/telephony/java/android/telephony/mbms/DownloadStatus.aidl b/telephony/java/android/telephony/mbms/DownloadStatus.aidl
new file mode 100755
index 0000000..e7cfd39
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadStatus.aidl
@@ -0,0 +1,19 @@
+/*
+** 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
new file mode 100644
index 0000000..90eb53f
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadStatus.java
@@ -0,0 +1,77 @@
+/*
+ * 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.aidl b/telephony/java/android/telephony/mbms/FileInfo.aidl
new file mode 100755
index 0000000..62926e1
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/FileInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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;
+
+parcelable FileInfo;
diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java
new file mode 100644
index 0000000..d3888bd
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/FileInfo.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A Parcelable class Cell-Broadcast downloadable file information.
+ * @hide
+ */
+public class FileInfo implements Parcelable {
+
+    /**
+     * The URI into the carriers infrastructure which points to this file.
+     * This is used internally but is also one of the few pieces of data about the content that is
+     * exposed and may be needed for disambiguation by the application.
+     */
+    final Uri uri;
+
+    /**
+     * The mime type of the content.
+     */
+    final String mimeType;
+
+    /**
+     * The size of the file in bytes.
+     */
+    final long size;
+
+    /**
+     * The MD5 hash of the file.
+     */
+    final byte md5Hash[];
+
+    /**
+     * Gets the parent service for this file.
+     */
+    public FileServiceInfo getFileServiceInfo() {
+        return null;
+    }
+
+    public static final Parcelable.Creator<FileInfo> CREATOR =
+            new Parcelable.Creator<FileInfo>() {
+        @Override
+        public FileInfo createFromParcel(Parcel source) {
+            return new FileInfo(source);
+        }
+
+        @Override
+        public FileInfo[] newArray(int size) {
+            return new FileInfo[size];
+        }
+    };
+
+    private FileInfo(Parcel in) {
+        uri = in.readParcelable(null);
+        mimeType = in.readString();
+        size = in.readLong();
+        int arraySize = in.readInt();
+        md5Hash = new byte[arraySize];
+        in.readByteArray(md5Hash);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(uri, flags);
+        dest.writeString(mimeType);
+        dest.writeLong(size);
+        dest.writeInt(md5Hash.length);
+        dest.writeByteArray(md5Hash);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.aidl b/telephony/java/android/telephony/mbms/FileServiceInfo.aidl
new file mode 100755
index 0000000..4646bad
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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;
+
+parcelable FileServiceInfo;
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
new file mode 100644
index 0000000..8bda370
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * A Parcelable class Cell-Broadcast downloadable file information.
+ * @hide
+ */
+public class FileServiceInfo extends ServiceInfo implements Parcelable {
+    public List<FileInfo> files;
+
+    public FileServiceInfo(Map<Locale, String> newNames, String newClassName, Locale newLocale,
+            String newServiceId, Date start, Date end, List<FileInfo> newFiles) {
+        super(newNames, newClassName, newLocale, newServiceId, start, end);
+        files = new ArrayList(newFiles);
+    }
+
+    public static final Parcelable.Creator<FileServiceInfo> CREATOR =
+            new Parcelable.Creator<FileServiceInfo>() {
+        @Override
+        public FileServiceInfo createFromParcel(Parcel source) {
+            return new FileServiceInfo(source);
+        }
+
+        @Override
+        public FileServiceInfo[] newArray(int size) {
+            return new FileServiceInfo[size];
+        }
+    };
+
+    FileServiceInfo(Parcel in) {
+        super(in);
+        files = new ArrayList<FileInfo>();
+        in.readList(files, null);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeList(files);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/IDownloadCallback.aidl b/telephony/java/android/telephony/mbms/IDownloadCallback.aidl
new file mode 100755
index 0000000..a6bd7e5
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/IDownloadCallback.aidl
@@ -0,0 +1,34 @@
+/*
+** 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;
+
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.FileInfo;
+
+/**
+ * The optional interface used by download clients to track progress.
+ * @hide
+ */
+interface IDownloadCallback
+{
+    /**
+     * 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);
+}
diff --git a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl
new file mode 100755
index 0000000..03227d0
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl
@@ -0,0 +1,42 @@
+/*
+** 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;
+
+import android.telephony.mbms.FileServiceInfo;
+
+import java.util.List;
+
+/**
+ * The interface the clients top-level file download listener will satisfy.
+ * @hide
+ */
+interface IMbmsDownloadManagerCallback
+{
+    void error(int errorCode, String message);
+
+    /**
+     * Called to indicate published File Services have changed.
+     *
+     * This will only be called after the application has requested
+     * a list of file services and specified a service class list
+     * of interest AND the results of a subsequent getFileServices
+     * call with the same service class list would
+     * return different
+     * results.
+     */
+    void fileServicesUpdated(in List<FileServiceInfo> services);
+}
diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
new file mode 100755
index 0000000..cbf0fca
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
@@ -0,0 +1,53 @@
+/*
+** 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;
+
+import android.telephony.mbms.StreamingServiceInfo;
+
+import java.util.List;
+
+/**
+ * The interface the clients top-level streaming listener will satisfy.
+ * @hide
+ */
+interface IMbmsStreamingManagerCallback
+{
+    void error(int errorCode, String message);
+
+    /**
+     * Called to indicate published Streaming Services have changed.
+     *
+     * This will only be called after the application has requested
+     * a list of streaming services and specified a service class list
+     * of interest AND the results of a subsequent getStreamServices
+     * call with the same service class list would
+     * return different
+     * results.
+     */
+    void streamingServicesUpdated(in List<StreamingServiceInfo> services);
+
+    /**
+     * 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
+     */
+    void activeStreamingServicesUpdated(in List<StreamingServiceInfo> services);
+}
diff --git a/core/java/android/bluetooth/le/IAdvertiserCallback.aidl b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
old mode 100644
new mode 100755
similarity index 62%
rename from core/java/android/bluetooth/le/IAdvertiserCallback.aidl
rename to telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
index c58b1df..891edad
--- a/core/java/android/bluetooth/le/IAdvertiserCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.bluetooth.le;
 
-import android.bluetooth.le.AdvertiseSettings;
+package android.telephony.mbms;
+
+import android.net.Uri;
 
 /**
- * Callback definitions for interacting with Advertiser
  * @hide
  */
-oneway interface IAdvertiserCallback {
-    void onAdvertiserRegistered(in int status, in int advertiserId);
-
-    void onMultiAdvertiseCallback(in int status, boolean isStart,
-                                  in AdvertiseSettings advertiseSettings);
+oneway interface IStreamingServiceCallback {
+    void error(int errorCode, String message);
+    void streamStateChanged(int state);
+    void uriUpdated(in Uri uri);
+    void broadcastSignalStrengthUpdated(int signalStrength);
 }
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
new file mode 100644
index 0000000..16fafe4
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import java.util.List;
+
+/**
+ * A Parcelable class with Cell-Broadcast service information.
+ * @hide
+ */
+public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.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) {
+        // default implementation empty
+    }
+
+    /**
+     * Called to indicate published File Services have changed.
+     *
+     * This will only be called after the application has requested
+     * a list of file services and specified a service class list
+     * of interest AND the results of a subsequent getFileServices
+     * call with the same service class list would return different
+     * results.
+     *
+     * @param services a List of FileServiceInfos
+     *
+     */
+    public void fileServicesUpdated(List<FileServiceInfo> services) {
+        // default implementation empty
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java
new file mode 100644
index 0000000..ac14c4f
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsException.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/** @hide */
+public class MbmsException extends Exception {
+    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_UNKNOWN_REMOTE_EXCEPTION = 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;
+
+    private final int mErrorCode;
+
+    /** @hide
+     * TODO: future systemapi
+     */
+    public MbmsException(int errorCode) {
+        super();
+        mErrorCode = errorCode;
+    }
+
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
new file mode 100644
index 0000000..b3bc814
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import java.util.List;
+
+/**
+ * A Parcelable class with Cell-Broadcast service information.
+ * @hide
+ */
+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) {
+        // default implementation empty
+    }
+
+    /**
+     * Called to indicate published Streaming Services have changed.
+     *
+     * This will only be called after the application has requested
+     * a list of streaming services and specified a service class list
+     * of interest AND the results of a subsequent getStreamServices
+     * call with the same service class list would return different
+     * results.
+     *
+     * @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) {
+        // default implementation empty
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.aidl b/telephony/java/android/telephony/mbms/ServiceInfo.aidl
new file mode 100755
index 0000000..6661c26
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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;
+
+parcelable ServiceInfo;
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
new file mode 100644
index 0000000..f167f0ab
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -0,0 +1,161 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Parcelable class with Cell-Broadcast service information.
+ * @hide
+ */
+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;
+
+    /**
+     * The language for this service content
+     */
+    final Locale locale;
+
+    /**
+     * 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;
+
+
+    public ServiceInfo(Map<Locale, String> newNames, String newClassName, Locale newLocale,
+            String newServiceId, Date start, Date end) {
+        if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName)
+                || newLocale == null || TextUtils.isEmpty(newServiceId)
+                || start == null || end == null) {
+            throw new IllegalArgumentException("Bad ServiceInfo construction");
+        }
+        if (newNames.size() > MAP_LIMIT) {
+            throw new RuntimeException("bad map length" + newNames.size());
+        }
+        names = new HashMap(newNames.size());
+        names.putAll(newNames);
+        className = newClassName;
+        locale = (Locale)newLocale.clone();
+        serviceId = newServiceId;
+        sessionStartTime = (Date)start.clone();
+        sessionEndTime = (Date)end.clone();
+    }
+
+    public static final Parcelable.Creator<FileServiceInfo> CREATOR =
+            new Parcelable.Creator<FileServiceInfo>() {
+        @Override
+        public FileServiceInfo createFromParcel(Parcel source) {
+            return new FileServiceInfo(source);
+        }
+
+        @Override
+        public FileServiceInfo[] newArray(int size) {
+            return new FileServiceInfo[size];
+        }
+    };
+
+    ServiceInfo(Parcel in) {
+        int mapCount = in.readInt();
+        if (mapCount > MAP_LIMIT || mapCount < 0) {
+              throw new RuntimeException("bad map length" + mapCount);
+        }
+        names = new HashMap(mapCount);
+        while (mapCount-- > 0) {
+            Locale locale = (java.util.Locale) in.readSerializable();
+            String name = in.readString();
+            names.put(locale, name);
+        }
+        className = in.readString();
+        locale = (java.util.Locale) in.readSerializable();
+        serviceId = in.readString();
+        sessionStartTime = (java.util.Date) in.readSerializable();
+        sessionEndTime = (java.util.Date) in.readSerializable();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        Set<Locale> keySet = names.keySet();
+        dest.writeInt(keySet.size());
+        for (Locale l : keySet) {
+            dest.writeSerializable(l);
+            dest.writeString(names.get(l));
+        }
+        dest.writeString(className);
+        dest.writeSerializable(locale);
+        dest.writeString(serviceId);
+        dest.writeSerializable(sessionStartTime);
+        dest.writeSerializable(sessionEndTime);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public Map<Locale, String> getNames() {
+        return names;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public Locale getLocale() {
+        return locale;
+    }
+
+    public String getServiceId() {
+        return serviceId;
+    }
+
+    public Date getSessionStartTime() {
+        return sessionStartTime;
+    }
+
+    public Date getSessionEndTime() {
+        return sessionEndTime;
+    }
+
+}
diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java
new file mode 100644
index 0000000..745cb98
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/StreamingService.java
@@ -0,0 +1,128 @@
+/*
+ * 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;
+
+import android.net.Uri;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.telephony.mbms.vendor.IMbmsStreamingService;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class StreamingService {
+    private static final String LOG_TAG = "MbmsStreamingService";
+    public final static int STATE_STOPPED = 1;
+    public final static int STATE_STARTED = 2;
+    public final static int STATE_STALLED = 3;
+
+    private final String mAppName;
+    private final int mSubscriptionId;
+    private final StreamingServiceInfo mServiceInfo;
+    private final IStreamingServiceCallback mCallback;
+
+    private IMbmsStreamingService mService;
+    /**
+     * @hide
+     */
+    public StreamingService(String appName,
+            int subscriptionId,
+            IMbmsStreamingService service,
+            StreamingServiceInfo streamingServiceInfo,
+            IStreamingServiceCallback callback) {
+        mAppName = appName;
+        mSubscriptionId = subscriptionId;
+        mService = service;
+        mServiceInfo = streamingServiceInfo;
+        mCallback = callback;
+    }
+
+    /**
+     * Retreive the Uri used to play this stream.
+     *
+     * This may throw a {@link MbmsException} with the error codes
+     * {@link MbmsException#ERROR_UNKNOWN_REMOTE_EXCEPTION} or
+     * {@link MbmsException#ERROR_SERVICE_LOST}
+     *
+     * @return The {@link Uri} to pass to the streaming client.
+     */
+    public Uri getPlaybackUri() throws MbmsException {
+        if (mService == null) {
+            throw new IllegalStateException("No streaming service attached");
+        }
+
+        try {
+            return mService.getPlaybackUri(mAppName, mSubscriptionId, mServiceInfo.getServiceId());
+        } catch (DeadObjectException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService = null;
+            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Caught remote exception calling getPlaybackUri: " + e);
+            throw new MbmsException(MbmsException.ERROR_UNKNOWN_REMOTE_EXCEPTION);
+        }
+    }
+
+    /**
+     * Retreive the info for this StreamingService.
+     */
+    public StreamingServiceInfo getInfo() {
+        return mServiceInfo;
+    }
+
+    /**
+     * Stop streaming this service.
+     * This may throw a {@link MbmsException} with the error code
+     * {@link MbmsException#ERROR_UNKNOWN_REMOTE_EXCEPTION} or
+     * {@link MbmsException#ERROR_SERVICE_LOST}
+     */
+    public void stopStreaming() throws MbmsException {
+        if (mService == null) {
+            throw new IllegalStateException("No streaming service attached");
+        }
+
+        try {
+            mService.stopStreaming(mAppName, mSubscriptionId, mServiceInfo.getServiceId());
+        } catch (DeadObjectException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService = null;
+            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Caught remote exception calling stopStreaming: " + e);
+            throw new MbmsException(MbmsException.ERROR_UNKNOWN_REMOTE_EXCEPTION);
+        }
+    }
+
+    public void dispose() throws MbmsException {
+        if (mService == null) {
+            throw new IllegalStateException("No streaming service attached");
+        }
+
+        try {
+            mService.disposeStream(mAppName, mSubscriptionId, mServiceInfo.getServiceId());
+        } catch (DeadObjectException e) {
+            Log.w(LOG_TAG, "Remote process died");
+            mService = null;
+            throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Caught remote exception calling dispose: " + e);
+            throw new MbmsException(MbmsException.ERROR_UNKNOWN_REMOTE_EXCEPTION);
+        }
+    }
+}
+
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
new file mode 100644
index 0000000..bd0a1b3
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import android.net.Uri;
+
+/**
+ * A Callback class for use when the application is actively streaming content.
+ * @hide
+ */
+public class StreamingServiceCallback extends IStreamingServiceCallback.Stub {
+
+    /**
+     * Indicates broadcast signal strength is not available for this service.
+     *
+     * This may be due to the service no longer being available due to geography
+     * or timing (end of service) or because lack of demand has caused the service
+     * to be delivered via unicast.
+     */
+    public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1;
+
+    public void error(int errorCode, String message) {
+        // default implementation empty
+    }
+
+    /**
+     * Called to indicate this stream has changed state.
+     *
+     * See {@link StreamingService#STATE_STOPPED}, {@link StreamingService#STATE_STARTED}
+     * and {@link StreamingService#STATE_STALLED}.
+     */
+    public void streamStateChanged(int state) {
+        // default implementation empty
+    }
+
+    /**
+     * Called to indicate published Download Services have changed.
+     *
+     * This may be called when a looping stream hits the end or
+     * when the a new URI should be used to correct for time drift.
+     */
+    public void uriUpdated(Uri uri) {
+        // default implementation empty
+    }
+
+    /**
+     * Broadcast Signal Strength updated.
+     *
+     * This signal strength is the BROADCAST signal strength which,
+     * depending on technology in play and it's deployment, may be
+     * stronger or weaker than the traditional UNICAST signal
+     * strength.  It a simple int from 0-4 for valid levels or
+     * {@link #SIGNAL_STRENGTH_UNAVAILABLE} if broadcast is not available
+     * for this service due to timing, geography or popularity.
+     */
+    public void broadcastSignalStrengthUpdated(int signalStrength) {
+        // default implementation empty
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl b/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl
new file mode 100755
index 0000000..b902f27
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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;
+
+parcelable StreamingServiceInfo;
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
new file mode 100644
index 0000000..f559585
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Date;
+import java.util.Locale;
+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
+ */
+public class StreamingServiceInfo extends ServiceInfo implements Parcelable {
+
+    public StreamingServiceInfo(Map<Locale, String> newNames, String newClassName,
+            Locale newLocale, String newServiceId, Date start, Date end) {
+        super(newNames, newClassName, newLocale, newServiceId, start, end);
+    }
+
+    public static final Parcelable.Creator<StreamingServiceInfo> CREATOR =
+            new Parcelable.Creator<StreamingServiceInfo>() {
+        @Override
+        public StreamingServiceInfo createFromParcel(Parcel source) {
+            return new StreamingServiceInfo(source);
+        }
+
+        @Override
+        public StreamingServiceInfo[] newArray(int size) {
+            return new StreamingServiceInfo[size];
+        }
+    };
+
+    StreamingServiceInfo(Parcel in) {
+        super(in);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.aidl b/telephony/java/android/telephony/mbms/UriPathPair.aidl
new file mode 100755
index 0000000..8bf76824
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/UriPathPair.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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;
+
+parcelable UriPathPair;
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.java b/telephony/java/android/telephony/mbms/UriPathPair.java
new file mode 100644
index 0000000..7acc270
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/UriPathPair.java
@@ -0,0 +1,80 @@
+/*
+ * 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.content.ContentResolver;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class UriPathPair implements Parcelable {
+    private final Uri mFilePathUri;
+    private final Uri mContentUri;
+
+    /** @hide */
+    public UriPathPair(Uri fileUri, Uri contentUri) {
+        if (fileUri == null || !ContentResolver.SCHEME_FILE.equals(fileUri.getScheme())) {
+            throw new IllegalArgumentException("File URI must have file scheme");
+        }
+        if (contentUri == null || !ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) {
+            throw new IllegalArgumentException("Content URI must have content scheme");
+        }
+
+        mFilePathUri = fileUri;
+        mContentUri = contentUri;
+    }
+
+    /** @hide */
+    protected UriPathPair(Parcel in) {
+        mFilePathUri = in.readParcelable(Uri.class.getClassLoader());
+        mContentUri = in.readParcelable(Uri.class.getClassLoader());
+    }
+
+    public static final Creator<UriPathPair> CREATOR = new Creator<UriPathPair>() {
+        @Override
+        public UriPathPair createFromParcel(Parcel in) {
+            return new UriPathPair(in);
+        }
+
+        @Override
+        public UriPathPair[] newArray(int size) {
+            return new UriPathPair[size];
+        }
+    };
+
+    /** future systemapi */
+    public Uri getFilePathUri() {
+        return mFilePathUri;
+    }
+
+    /** future systemapi */
+    public Uri getContentUri() {
+        return mContentUri;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mFilePathUri, flags);
+        dest.writeParcelable(mContentUri, flags);
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
new file mode 100755
index 0000000..3090e12
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
@@ -0,0 +1,73 @@
+/*
+** 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.vendor;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStatus;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
+import android.telephony.mbms.IDownloadCallback;
+
+/**
+ * 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);
+
+    /**
+     * - 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);
+
+    /**
+     * should move the params into a DownloadRequest parcelable
+     */
+    int download(String appName, in DownloadRequest downloadRequest, IDownloadCallback listener);
+
+    List<DownloadRequest> listPendingDownloads(String appName);
+
+    int cancelDownload(String appName, in DownloadRequest downloadRequest);
+
+    DownloadStatus getDownloadStatus(String appName, in DownloadRequest downloadRequest);
+
+    /*
+     * 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(String appName, 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);
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
new file mode 100755
index 0000000..8ff7fa7
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
@@ -0,0 +1,54 @@
+/*
+** 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.vendor;
+
+import android.net.Uri;
+import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IStreamingServiceCallback;
+import android.telephony.mbms.StreamingServiceInfo;
+
+/**
+ * The interface the opaque MbmsStreamingService will satisfy.
+ * @hide
+ */
+interface IMbmsStreamingService
+{
+    int initialize(IMbmsStreamingManagerCallback listener, String appName, int subId);
+
+    int getStreamingServices(String appName, int subId, in List<String> serviceClasses);
+
+    int startStreaming(String appName, int subId, String serviceId,
+            IStreamingServiceCallback listener);
+
+    /**
+     * Per-stream api.  Note each specifies what stream they apply to.
+     */
+
+    Uri getPlaybackUri(String appName, int subId, String serviceId);
+
+    void stopStreaming(String appName, 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);
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
new file mode 100644
index 0000000..369aef1
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms.vendor;
+
+import android.os.RemoteException;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStatus;
+import android.telephony.mbms.IDownloadCallback;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
+
+import java.util.List;
+
+/**
+ * Base class for MbmsDownloadService. The middleware should extend this base class rather than
+ * the aidl stub for compatibility
+ * @hide
+ * TODO: future systemapi
+ */
+public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
+    @Override
+    public void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener)
+            throws RemoteException {
+    }
+
+    @Override
+    public int getFileServices(String appName, int subId, List<String> serviceClasses) throws
+            RemoteException {
+        return 0;
+    }
+
+    @Override
+    public int download(String appName, DownloadRequest downloadRequest, IDownloadCallback listener)
+            throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public List<DownloadRequest> listPendingDownloads(String appName) throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public int cancelDownload(String appName, DownloadRequest downloadRequest)
+            throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public DownloadStatus getDownloadStatus(String appName, DownloadRequest downloadRequest)
+            throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void resetDownloadKnowledge(String appName, DownloadRequest downloadRequest)
+            throws RemoteException {
+    }
+
+    @Override
+    public void dispose(String appName, int subId) throws RemoteException {
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
new file mode 100644
index 0000000..5b74312
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -0,0 +1,150 @@
+/*
+ * 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.vendor;
+
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IStreamingServiceCallback;
+import android.telephony.mbms.MbmsException;
+
+import java.util.List;
+
+/**
+ * @hide
+ * TODO: future 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}
+     *
+     * @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 {
+        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 IMbmsStreamingManagerCallback#streamingServicesUpdated(List)}
+     *
+     * Note that subsequent calls with the same uid, appName 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}
+     */
+    @Override
+    public int getStreamingServices(String appName, int subscriptionId,
+            List<String> serviceClasses) throws RemoteException {
+        return 0;
+    }
+
+    /**
+     * 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)}.
+     *
+     * 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 TODO: document possible errors
+     */
+    @Override
+    public int startStreaming(String appName, int subscriptionId,
+            String serviceId, IStreamingServiceCallback listener) throws RemoteException {
+        return 0;
+    }
+
+    /**
+     * Retrieves the streaming URI for a particular service. If the middleware is not yet ready to
+     * stream the service, this method may return null.
+     *
+     * 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)
+            throws RemoteException {
+        return null;
+    }
+
+    /**
+     * 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)}.
+     * @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)
+            throws RemoteException {
+    }
+
+    /**
+     * 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
+     * used after this method has called by the app.
+     * @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)
+            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
+     * app is required for this operation, and the corresponding callback provided via
+     * {@link #initialize(IMbmsStreamingManagerCallback, String, int)} should no longer be used
+     * after this method has been called by the app.
+     * @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 {
+    }
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index 712816f..bb06d7e 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -37,7 +37,7 @@
 interface IImsServiceController {
     // ImsService Control
     void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c);
-    void removeImsFeature(int slotId, int feature);
+    void removeImsFeature(int slotId, int feature, IImsFeatureStatusCallback c);
     // MMTel Feature
     int startSession(int slotId, int featureType, in PendingIntent incomingCallIntent,
             in IImsRegistrationListener listener);
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index f01e4c0..256e13b 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -106,6 +106,7 @@
     public static final int EVENT_REDIRECTION_DETECTED = BASE + 44;
     public static final int EVENT_PCO_DATA_RECEIVED = BASE + 45;
     public static final int EVENT_SET_CARRIER_DATA_ENABLED = BASE + 46;
+    public static final int EVENT_DATA_RECONNECT = BASE + 47;
 
     /***** Constants *****/
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 5c37822..fb6782e 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -16,8 +16,11 @@
 
 package com.android.internal.telephony;
 
+import android.app.PendingIntent;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Messenger;
 import android.os.ResultReceiver;
 import android.net.Uri;
 import android.service.carrier.CarrierIdentifier;
@@ -28,8 +31,10 @@
 import android.telephony.IccOpenLogicalChannelResponse;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
+import android.telephony.NetworkScanRequest;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.TelephonyHistogram;
 import android.telephony.VisualVoicemailSmsFilterSettings;
 import com.android.ims.internal.IImsServiceController;
@@ -273,6 +278,16 @@
      */
     boolean handlePinMmi(String dialString);
 
+
+    /**
+     * Handles USSD commands.
+     *
+     * @param subId The subscription to use.
+     * @param ussdRequest the USSD command to be executed.
+     * @param wrappedCallback receives a callback result.
+     */
+    void handleUssdRequest(int subId, String ussdRequest, in ResultReceiver wrappedCallback);
+
     /**
      * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated
      * without SEND (so <code>dial</code> is not appropriate) for
@@ -356,7 +371,7 @@
     /**
      * Report whether data connectivity is possible.
      */
-    boolean isDataConnectivityPossible();
+    boolean isDataConnectivityPossible(int subId);
 
     Bundle getCellLocation(String callingPkg);
 
@@ -481,12 +496,20 @@
      */
     int getVoiceMessageCountForSubscriber(int subId);
 
+    /**
+      * Returns true if current state supports both voice and data
+      * simultaneously. This can change based on location or network condition.
+      */
+    boolean isConcurrentVoiceAndDataAllowed(int subId);
+
     oneway void setVisualVoicemailEnabled(String callingPackage,
             in PhoneAccountHandle accountHandle, boolean enabled);
 
     boolean isVisualVoicemailEnabled(String callingPackage,
             in PhoneAccountHandle accountHandle);
 
+    String getVisualVoicemailPackageName(String callingPackage, int subId);
+
     // Not oneway, caller needs to make sure the vaule is set before receiving a SMS
     void enableVisualVoicemailSmsFilter(String callingPackage, int subId,
             in VisualVoicemailSmsFilterSettings settings);
@@ -497,9 +520,18 @@
     VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings(String callingPackage,
             int subId);
 
-    // Get settings set by the package, requires READ_PRIVILEGED_PHONE_STATE permission
-    VisualVoicemailSmsFilterSettings getSystemVisualVoicemailSmsFilterSettings(String packageName,
-            int subId);
+    /**
+     *  Get settings set by the current default dialer, Internal use only.
+     *  Requires READ_PRIVILEGED_PHONE_STATE permission.
+     */
+    VisualVoicemailSmsFilterSettings getActiveVisualVoicemailSmsFilterSettings(int subId);
+
+    /**
+     * Send a visual voicemail SMS. Internal use only.
+     * Requires caller to be the default dialer and have SEND_SMS permission
+     */
+    oneway void sendVisualVoicemailSmsForSubscriber(in String callingPackage, in int subId,
+            in String number, in int port, in String text, in PendingIntent sentIntent);
 
     /**
      * Returns the network type for data transmission
@@ -592,9 +624,10 @@
      *
      * @param subId The subscription to use.
      * @param AID Application id. See ETSI 102.221 and 101.220.
+     * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      */
-    IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID);
+    IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2);
 
     /**
      * Closes a previously opened logical channel to the ICC card.
@@ -765,6 +798,26 @@
     CellNetworkScanResult getCellNetworkScanResults(int subId);
 
     /**
+     * Perform a radio network scan and return the id of this scan.
+     *
+     * @param subId the id of the subscription.
+     * @param request Defines all the configs for network scan.
+     * @param messenger Callback messages will be sent using this messenger.
+     * @param binder the binder object instantiated in TelephonyManager.
+     * @return An id for this scan.
+     */
+    int requestNetworkScan(int subId, in NetworkScanRequest request, in Messenger messenger,
+            in IBinder binder);
+
+    /**
+     * Stop an existing radio network scan.
+     *
+     * @param subId the id of the subscription.
+     * @param scanId The id of the scan that is going to be stopped.
+     */
+    void stopNetworkScan(int subId, int scanId);
+
+    /**
      * Ask the radio to connect to the input network and change selection mode to manual.
      *
      * @param subId the id of the subscription.
@@ -1238,12 +1291,12 @@
     List<ClientRequestStats> getClientRequestStats(String callingPackage, int subid);
 
     /**
-     * Set SIM card power state. Request is equivalent to inserting or removing the card.
+     * Set SIM card power state.
      * @param slotIndex SIM slot id
-     * @param powerUp True if powering up the SIM, otherwise powering down
+     * @param state  State of SIM (power down, power up, pass through)
      * @hide
      * */
-    void setSimPowerStateForSlot(int slotIndex, boolean powerUp);
+    void setSimPowerStateForSlot(int slotIndex, int state);
 
     /**
      * Returns a list of Forbidden PLMNs from the specified SIM App
@@ -1256,4 +1309,23 @@
      * @param appType the icc application type, like {@link #APPTYPE_USIM}
      */
     String[] getForbiddenPlmns(int subId, int appType);
+
+    /**
+     * Check if phone is in emergency callback mode
+     * @return true if phone is in emergency callback mode
+     * @param subId the subscription ID that this action applies to.
+     * @hide
+     */
+    boolean getEmergencyCallbackMode(int subId);
+
+    /**
+     * Get the most recently available signal strength information.
+     *
+     * Get the most recent SignalStrength information reported by the modem. Due
+     * to power saving this information may not always be current.
+     * @param subId Subscription index
+     * @return the most recent cached signal strength info from the modem
+     * @hide
+     */
+    SignalStrength getSignalStrength(int subId);
 }
diff --git a/telephony/java/com/android/internal/telephony/NetworkScanResult.java b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
new file mode 100644
index 0000000..0099961
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
@@ -0,0 +1,129 @@
+/*
+ * 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.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.CellInfo;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines the incremental network scan result.
+ *
+ * This class contains the network scan results. When the user starts a new scan, multiple
+ * NetworkScanResult may be returned, containing either the scan result or error. When the user
+ * stops an ongoing scan, only one NetworkScanResult will be returned to indicate either the scan
+ * is now complete or there is some error stopping it.
+ * @hide
+ */
+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;
+
+    // Contains the last part of the scan result and the scan is now complete.
+    public static final int SCAN_STATUS_COMPLETE = 1;
+
+    // The status of the scan, only valid when scanError = SUCCESS.
+    public int scanStatus;
+
+    /**
+     * The error code of the scan
+     *
+     * This is the error code returned from the RIL, see {@link RILConstants} for more details
+     */
+    public int scanError;
+
+    // The scan results, only valid when scanError = SUCCESS.
+    public List<CellInfo> networkInfos;
+
+    /**
+     * Creates a new NetworkScanResult with scanStatus, scanError and networkInfos
+     *
+     * @param scanStatus The status of the scan.
+     * @param scanError The error code of the scan.
+     * @param networkInfos List of the CellInfo.
+     */
+    public NetworkScanResult(int scanStatus, int scanError, List<CellInfo> networkInfos) {
+        this.scanStatus = scanStatus;
+        this.scanError = scanError;
+        this.networkInfos = networkInfos;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(scanStatus);
+        dest.writeInt(scanError);
+        CellInfo[] ci = networkInfos.toArray(new CellInfo[networkInfos.size()]);
+        dest.writeParcelableArray(ci, flags);
+    }
+
+    private NetworkScanResult(Parcel in) {
+        scanStatus = in.readInt();
+        scanError = in.readInt();
+        CellInfo[] ci = (CellInfo[]) in.readParcelableArray(
+                Object.class.getClassLoader(),
+                CellInfo.class);
+        networkInfos = Arrays.asList(ci);
+    }
+
+    @Override
+    public boolean equals (Object o) {
+        NetworkScanResult nsr;
+
+        try {
+            nsr = (NetworkScanResult) o;
+        } catch (ClassCastException ex) {
+            return false;
+        }
+
+        if (o == null) {
+            return false;
+        }
+
+        return (scanStatus == nsr.scanStatus
+                && scanError == nsr.scanError
+                && networkInfos.equals(nsr.networkInfos));
+    }
+
+    @Override
+    public int hashCode () {
+        return ((scanStatus * 31)
+                + (scanError * 23)
+                + (networkInfos.hashCode() * 37));
+    }
+
+    public static final Creator<NetworkScanResult> CREATOR =
+        new Creator<NetworkScanResult>() {
+            @Override
+            public NetworkScanResult createFromParcel(Parcel in) {
+                return new NetworkScanResult(in);
+            }
+
+            @Override
+            public NetworkScanResult[] newArray(int size) {
+                return new NetworkScanResult[size];
+            }
+        };
+}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
new file mode 100644
index 0000000..f7f0f29
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.android.internal.telephony;
+
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.PreciseCallState;
+
+import com.android.internal.telephony.PhoneConstants;
+
+import java.util.List;
+
+public class PhoneConstantConversions {
+    /**
+     * Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
+     * constants for the public API.
+     */
+    public static int convertCallState(PhoneConstants.State state) {
+        switch (state) {
+            case RINGING:
+                return TelephonyManager.CALL_STATE_RINGING;
+            case OFFHOOK:
+                return TelephonyManager.CALL_STATE_OFFHOOK;
+            default:
+                return TelephonyManager.CALL_STATE_IDLE;
+        }
+    }
+
+    /**
+     * Convert the TelephonyManager.CALL_STATE_* constants into the
+     * {@link PhoneConstants.State} enum for the public API.
+     */
+    public static PhoneConstants.State convertCallState(int state) {
+        switch (state) {
+            case TelephonyManager.CALL_STATE_RINGING:
+                return PhoneConstants.State.RINGING;
+            case TelephonyManager.CALL_STATE_OFFHOOK:
+                return PhoneConstants.State.OFFHOOK;
+            default:
+                return PhoneConstants.State.IDLE;
+        }
+    }
+
+    /**
+     * Convert the {@link PhoneConstants.DataState} enum into the TelephonyManager.DATA_* constants
+     * for the public API.
+     */
+    public static int convertDataState(PhoneConstants.DataState state) {
+        switch (state) {
+            case CONNECTING:
+                return TelephonyManager.DATA_CONNECTING;
+            case CONNECTED:
+                return TelephonyManager.DATA_CONNECTED;
+            case SUSPENDED:
+                return TelephonyManager.DATA_SUSPENDED;
+            default:
+                return TelephonyManager.DATA_DISCONNECTED;
+        }
+    }
+
+    /**
+     * Convert the TelephonyManager.DATA_* constants into {@link PhoneConstants.DataState} enum
+     * for the public API.
+     */
+    public static PhoneConstants.DataState convertDataState(int state) {
+        switch (state) {
+            case TelephonyManager.DATA_CONNECTING:
+                return PhoneConstants.DataState.CONNECTING;
+            case TelephonyManager.DATA_CONNECTED:
+                return PhoneConstants.DataState.CONNECTED;
+            case TelephonyManager.DATA_SUSPENDED:
+                return PhoneConstants.DataState.SUSPENDED;
+            default:
+                return PhoneConstants.DataState.DISCONNECTED;
+        }
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 1e1d7a6..e2d25b8 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -414,6 +414,9 @@
     int RIL_REQUEST_SEND_DEVICE_STATE = 138;
     int RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER = 139;
     int RIL_REQUEST_SET_SIM_CARD_POWER = 140;
+    int RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION = 141;
+    int RIL_REQUEST_START_NETWORK_SCAN = 142;
+    int RIL_REQUEST_STOP_NETWORK_SCAN = 143;
 
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
 
@@ -465,4 +468,7 @@
     int RIL_UNSOL_STK_CC_ALPHA_NOTIFY = 1044;
     int RIL_UNSOL_LCEDATA_RECV = 1045;
     int RIL_UNSOL_PCO_DATA = 1046;
+    int RIL_UNSOL_MODEM_RESTART = 1047;
+    int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048;
+    int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049;
 }
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
new file mode 100644
index 0000000..439eaea
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2014 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.telephony;
+
+import android.telephony.Rlog;
+import android.os.Build;
+import android.util.SparseIntArray;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.telephony.cdma.sms.UserData;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+public class Sms7BitEncodingTranslator {
+    private static final String TAG = "Sms7BitEncodingTranslator";
+    private static final boolean DBG = Build.IS_DEBUGGABLE ;
+    private static boolean mIs7BitTranslationTableLoaded = false;
+    private static SparseIntArray mTranslationTable = null;
+    private static SparseIntArray mTranslationTableCommon = null;
+    private static SparseIntArray mTranslationTableGSM = null;
+    private static SparseIntArray mTranslationTableCDMA = null;
+
+    // Parser variables
+    private static final String XML_START_TAG = "SmsEnforce7BitTranslationTable";
+    private static final String XML_TRANSLATION_TYPE_TAG = "TranslationType";
+    private static final String XML_CHARACTOR_TAG = "Character";
+    private static final String XML_FROM_TAG = "from";
+    private static final String XML_TO_TAG = "to";
+
+    /**
+     * Translates each message character that is not supported by GSM 7bit
+     * alphabet into a supported one
+     *
+     * @param message
+     *            message to be translated
+     * @param throwsException
+     *            if true and some error occurs during translation, an exception
+     *            is thrown; otherwise a null String is returned
+     * @return translated message or null if some error occur
+     */
+    public static String translate(CharSequence message) {
+        if (message == null) {
+            Rlog.w(TAG, "Null message can not be translated");
+            return null;
+        }
+
+        int size = message.length();
+        if (size <= 0) {
+            return "";
+        }
+
+        if (!mIs7BitTranslationTableLoaded) {
+            mTranslationTableCommon = new SparseIntArray();
+            mTranslationTableGSM = new SparseIntArray();
+            mTranslationTableCDMA = new SparseIntArray();
+            load7BitTranslationTableFromXml();
+            mIs7BitTranslationTableLoaded = true;
+        }
+
+        if ((mTranslationTableCommon != null && mTranslationTableCommon.size() > 0) ||
+                (mTranslationTableGSM != null && mTranslationTableGSM.size() > 0) ||
+                (mTranslationTableCDMA != null && mTranslationTableCDMA.size() > 0)) {
+            char[] output = new char[size];
+            boolean isCdmaFormat = useCdmaFormatForMoSms();
+            for (int i = 0; i < size; i++) {
+                output[i] = translateIfNeeded(message.charAt(i), isCdmaFormat);
+            }
+
+            return String.valueOf(output);
+        }
+
+        return null;
+    }
+
+    /**
+     * Translates a single character into its corresponding acceptable one, if
+     * needed, based on GSM 7-bit alphabet
+     *
+     * @param c
+     *            character to be translated
+     * @return original character, if it's present on GSM 7-bit alphabet; a
+     *         corresponding character, based on the translation table or white
+     *         space, if no mapping is found in the translation table for such
+     *         character
+     */
+    private static char translateIfNeeded(char c, boolean isCdmaFormat) {
+        if (noTranslationNeeded(c, isCdmaFormat)) {
+            if (DBG) {
+                Rlog.v(TAG, "No translation needed for " + Integer.toHexString(c));
+            }
+            return c;
+        }
+
+        /*
+         * Trying to translate unicode to Gsm 7-bit alphabet; If c is not
+         * present on translation table, c does not belong to Unicode Latin-1
+         * (Basic + Supplement), so we don't know how to translate it to a Gsm
+         * 7-bit character! We replace c for an empty space and advises the user
+         * about it.
+         */
+        int translation = -1;
+
+        if (mTranslationTableCommon != null) {
+            translation = mTranslationTableCommon.get(c, -1);
+        }
+
+        if (translation == -1) {
+            if (isCdmaFormat) {
+                if (mTranslationTableCDMA != null) {
+                    translation = mTranslationTableCDMA.get(c, -1);
+                }
+            } else {
+                if (mTranslationTableGSM != null) {
+                    translation = mTranslationTableGSM.get(c, -1);
+                }
+            }
+        }
+
+        if (translation != -1) {
+            if (DBG) {
+                Rlog.v(TAG, Integer.toHexString(c) + " (" + c + ")" + " translated to "
+                        + Integer.toHexString(translation) + " (" + (char) translation + ")");
+            }
+            return (char) translation;
+        } else {
+            if (DBG) {
+                Rlog.w(TAG, "No translation found for " + Integer.toHexString(c)
+                        + "! Replacing for empty space");
+            }
+            return ' ';
+        }
+    }
+
+    private static boolean noTranslationNeeded(char c, boolean isCdmaFormat) {
+        if (isCdmaFormat) {
+            return GsmAlphabet.isGsmSeptets(c) && UserData.charToAscii.get(c, -1) != -1;
+        }
+        else {
+            return GsmAlphabet.isGsmSeptets(c);
+        }
+    }
+
+    private static boolean useCdmaFormatForMoSms() {
+        if (!SmsManager.getDefault().isImsSmsSupported()) {
+            // use Voice technology to determine SMS format.
+            return TelephonyManager.getDefault().getCurrentPhoneType()
+                    == PhoneConstants.PHONE_TYPE_CDMA;
+        }
+        // IMS is registered with SMS support, check the SMS format supported
+        return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
+    }
+
+    /**
+     * Load the whole translation table file from the framework resource
+     * encoded in XML.
+     */
+    private static void load7BitTranslationTableFromXml() {
+        XmlResourceParser parser = null;
+        Resources r = Resources.getSystem();
+
+        if (parser == null) {
+            if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: open normal file");
+            parser = r.getXml(com.android.internal.R.xml.sms_7bit_translation_table);
+        }
+
+        try {
+            XmlUtils.beginDocument(parser, XML_START_TAG);
+            while (true)  {
+                XmlUtils.nextElement(parser);
+                String tag = parser.getName();
+                if (DBG) {
+                    Rlog.d(TAG, "tag: " + tag);
+                }
+                if (XML_TRANSLATION_TYPE_TAG.equals(tag)) {
+                    String type = parser.getAttributeValue(null, "Type");
+                    if (DBG) {
+                        Rlog.d(TAG, "type: " + type);
+                    }
+                    if (type.equals("common")) {
+                        mTranslationTable = mTranslationTableCommon;
+                    } else if (type.equals("gsm")) {
+                        mTranslationTable = mTranslationTableGSM;
+                    } else if (type.equals("cdma")) {
+                        mTranslationTable = mTranslationTableCDMA;
+                    } else {
+                        Rlog.e(TAG, "Error Parsing 7BitTranslationTable: found incorrect type" + type);
+                    }
+                } else if (XML_CHARACTOR_TAG.equals(tag) && mTranslationTable != null) {
+                    int from = parser.getAttributeUnsignedIntValue(null,
+                            XML_FROM_TAG, -1);
+                    int to = parser.getAttributeUnsignedIntValue(null,
+                            XML_TO_TAG, -1);
+                    if ((from != -1) && (to != -1)) {
+                        if (DBG) {
+                            Rlog.d(TAG, "Loading mapping " + Integer.toHexString(from)
+                                    .toUpperCase() + " -> " + Integer.toHexString(to)
+                                    .toUpperCase());
+                        }
+                        mTranslationTable.put (from, to);
+                    } else {
+                        Rlog.d(TAG, "Invalid translation table file format");
+                    }
+                } else {
+                    break;
+                }
+            }
+            if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: parsing successful, file loaded");
+        } catch (Exception e) {
+            Rlog.e(TAG, "Got exception while loading 7BitTranslationTable file.", e);
+        } finally {
+            if (parser instanceof XmlResourceParser) {
+                ((XmlResourceParser)parser).close();
+            }
+        }
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsAddress.java b/telephony/java/com/android/internal/telephony/SmsAddress.java
new file mode 100644
index 0000000..b3892cb
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsAddress.java
@@ -0,0 +1,65 @@
+/*
+ * 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.telephony;
+
+public abstract class SmsAddress {
+    // From TS 23.040 9.1.2.5 and TS 24.008 table 10.5.118
+    // and C.S0005-D table 2.7.1.3.2.4-2
+    public static final int TON_UNKNOWN = 0;
+    public static final int TON_INTERNATIONAL = 1;
+    public static final int TON_NATIONAL = 2;
+    public static final int TON_NETWORK = 3;
+    public static final int TON_SUBSCRIBER = 4;
+    public static final int TON_ALPHANUMERIC = 5;
+    public static final int TON_ABBREVIATED = 6;
+
+    public int ton;
+    public String address;
+    public byte[] origBytes;
+
+    /**
+     * Returns the address of the SMS message in String form or null if unavailable
+     */
+    public String getAddressString() {
+        return address;
+    }
+
+    /**
+     * Returns true if this is an alphanumeric address
+     */
+    public boolean isAlphanumeric() {
+        return ton == TON_ALPHANUMERIC;
+    }
+
+    /**
+     * Returns true if this is a network address
+     */
+    public boolean isNetworkSpecific() {
+        return ton == TON_NETWORK;
+    }
+
+    public boolean couldBeEmailGateway() {
+        // Some carriers seems to send email gateway messages in this form:
+        // from: an UNKNOWN TON, 3 or 4 digits long, beginning with a 5
+        // PID: 0x00, Data coding scheme 0x03
+        // So we just attempt to treat any message from an address length <= 4
+        // as an email gateway
+
+        return address.length() <= 4;
+    }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/java/com/android/internal/telephony/SmsApplication.java
new file mode 100644
index 0000000..0d1f205
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsApplication.java
@@ -0,0 +1,980 @@
+/*
+ * Copyright (C) 2013 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.telephony;
+
+import android.Manifest.permission;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.Rlog;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Class for managing the primary application that we will deliver SMS/MMS messages to
+ *
+ * {@hide}
+ */
+public final class SmsApplication {
+    static final String LOG_TAG = "SmsApplication";
+    private static final String PHONE_PACKAGE_NAME = "com.android.phone";
+    private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
+    private static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service";
+    private static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony";
+
+    private static final String SCHEME_SMS = "sms";
+    private static final String SCHEME_SMSTO = "smsto";
+    private static final String SCHEME_MMS = "mms";
+    private static final String SCHEME_MMSTO = "mmsto";
+    private static final boolean DEBUG_MULTIUSER = false;
+
+    private static SmsPackageMonitor sSmsPackageMonitor = null;
+
+    public static class SmsApplicationData {
+        /**
+         * Name of this SMS app for display.
+         */
+        private String mApplicationName;
+
+        /**
+         * Package name for this SMS app.
+         */
+        public String mPackageName;
+
+        /**
+         * The class name of the SMS_DELIVER_ACTION receiver in this app.
+         */
+        private String mSmsReceiverClass;
+
+        /**
+         * The class name of the WAP_PUSH_DELIVER_ACTION receiver in this app.
+         */
+        private String mMmsReceiverClass;
+
+        /**
+         * The class name of the ACTION_RESPOND_VIA_MESSAGE intent in this app.
+         */
+        private String mRespondViaMessageClass;
+
+        /**
+         * The class name of the ACTION_SENDTO intent in this app.
+         */
+        private String mSendToClass;
+
+        /**
+         * The class name of the ACTION_DEFAULT_SMS_PACKAGE_CHANGED receiver in this app.
+         */
+        private String mSmsAppChangedReceiverClass;
+
+        /**
+         * The class name of the ACTION_EXTERNAL_PROVIDER_CHANGE receiver in this app.
+         */
+        private String mProviderChangedReceiverClass;
+
+        /**
+         * The class name of the SIM_FULL_ACTION receiver in this app.
+         */
+        private String mSimFullReceiverClass;
+
+        /**
+         * The user-id for this application
+         */
+        private int mUid;
+
+        /**
+         * Returns true if this SmsApplicationData is complete (all intents handled).
+         * @return
+         */
+        public boolean isComplete() {
+            return (mSmsReceiverClass != null && mMmsReceiverClass != null
+                    && mRespondViaMessageClass != null && mSendToClass != null);
+        }
+
+        public SmsApplicationData(String packageName, int uid) {
+            mPackageName = packageName;
+            mUid = uid;
+        }
+
+        public String getApplicationName(Context context) {
+            if (mApplicationName == null) {
+                PackageManager pm = context.getPackageManager();
+                ApplicationInfo appInfo;
+                try {
+                    appInfo = pm.getApplicationInfoAsUser(mPackageName, 0,
+                            UserHandle.getUserId(mUid));
+                } catch (NameNotFoundException e) {
+                    return null;
+                }
+                if (appInfo != null) {
+                    CharSequence label  = pm.getApplicationLabel(appInfo);
+                    mApplicationName = (label == null) ? null : label.toString();
+                }
+            }
+            return mApplicationName;
+        }
+
+        @Override
+        public String toString() {
+            return " mPackageName: " + mPackageName
+                    + " mSmsReceiverClass: " + mSmsReceiverClass
+                    + " mMmsReceiverClass: " + mMmsReceiverClass
+                    + " mRespondViaMessageClass: " + mRespondViaMessageClass
+                    + " mSendToClass: " + mSendToClass
+                    + " mSmsAppChangedClass: " + mSmsAppChangedReceiverClass
+                    + " mProviderChangedReceiverClass: " + mProviderChangedReceiverClass
+                    + " mSimFullReceiverClass: " + mSimFullReceiverClass
+                    + " mUid: " + mUid;
+        }
+    }
+
+    /**
+     * Returns the userId of the Context object, if called from a system app,
+     * otherwise it returns the caller's userId
+     * @param context The context object passed in by the caller.
+     * @return
+     */
+    private static int getIncomingUserId(Context context) {
+        int contextUserId = context.getUserId();
+        final int callingUid = Binder.getCallingUid();
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "getIncomingUserHandle caller=" + callingUid + ", myuid="
+                    + android.os.Process.myUid() + "\n\t" + Debug.getCallers(4));
+        }
+        if (UserHandle.getAppId(callingUid)
+                < android.os.Process.FIRST_APPLICATION_UID) {
+            return contextUserId;
+        } else {
+            return UserHandle.getUserId(callingUid);
+        }
+    }
+
+    /**
+     * Returns the list of available SMS apps defined as apps that are registered for both the
+     * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
+     * receivers are enabled)
+     *
+     * Requirements to be an SMS application:
+     * Implement SMS_DELIVER_ACTION broadcast receiver.
+     * Require BROADCAST_SMS permission.
+     *
+     * Implement WAP_PUSH_DELIVER_ACTION broadcast receiver.
+     * Require BROADCAST_WAP_PUSH permission.
+     *
+     * Implement RESPOND_VIA_MESSAGE intent.
+     * Support smsto Uri scheme.
+     * Require SEND_RESPOND_VIA_MESSAGE permission.
+     *
+     * Implement ACTION_SENDTO intent.
+     * Support smsto Uri scheme.
+     */
+    public static Collection<SmsApplicationData> getApplicationCollection(Context context) {
+        int userId = getIncomingUserId(context);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return getApplicationCollectionInternal(context, userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private static Collection<SmsApplicationData> getApplicationCollectionInternal(
+            Context context, int userId) {
+        PackageManager packageManager = context.getPackageManager();
+
+        // Get the list of apps registered for SMS
+        Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
+        List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+                userId);
+
+        HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
+
+        // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers)
+        for (ResolveInfo resolveInfo : smsReceivers) {
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            if (activityInfo == null) {
+                continue;
+            }
+            if (!permission.BROADCAST_SMS.equals(activityInfo.permission)) {
+                continue;
+            }
+            final String packageName = activityInfo.packageName;
+            if (!receivers.containsKey(packageName)) {
+                final SmsApplicationData smsApplicationData = new SmsApplicationData(packageName,
+                        activityInfo.applicationInfo.uid);
+                smsApplicationData.mSmsReceiverClass = activityInfo.name;
+                receivers.put(packageName, smsApplicationData);
+            }
+        }
+
+        // Update any existing entries with mms receiver class
+        intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
+        intent.setDataAndType(null, "application/vnd.wap.mms-message");
+        List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+                userId);
+        for (ResolveInfo resolveInfo : mmsReceivers) {
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            if (activityInfo == null) {
+                continue;
+            }
+            if (!permission.BROADCAST_WAP_PUSH.equals(activityInfo.permission)) {
+                continue;
+            }
+            final String packageName = activityInfo.packageName;
+            final SmsApplicationData smsApplicationData = receivers.get(packageName);
+            if (smsApplicationData != null) {
+                smsApplicationData.mMmsReceiverClass = activityInfo.name;
+            }
+        }
+
+        // Update any existing entries with respond via message intent class.
+        intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE,
+                Uri.fromParts(SCHEME_SMSTO, "", null));
+        List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent, 0,
+                userId);
+        for (ResolveInfo resolveInfo : respondServices) {
+            final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+            if (serviceInfo == null) {
+                continue;
+            }
+            if (!permission.SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) {
+                continue;
+            }
+            final String packageName = serviceInfo.packageName;
+            final SmsApplicationData smsApplicationData = receivers.get(packageName);
+            if (smsApplicationData != null) {
+                smsApplicationData.mRespondViaMessageClass = serviceInfo.name;
+            }
+        }
+
+        // Update any existing entries with supports send to.
+        intent = new Intent(Intent.ACTION_SENDTO,
+                Uri.fromParts(SCHEME_SMSTO, "", null));
+        List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, 0,
+                userId);
+        for (ResolveInfo resolveInfo : sendToActivities) {
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            if (activityInfo == null) {
+                continue;
+            }
+            final String packageName = activityInfo.packageName;
+            final SmsApplicationData smsApplicationData = receivers.get(packageName);
+            if (smsApplicationData != null) {
+                smsApplicationData.mSendToClass = activityInfo.name;
+            }
+        }
+
+        // Update any existing entries with the default sms changed handler.
+        intent = new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+        List<ResolveInfo> smsAppChangedReceivers =
+                packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" +
+                    smsAppChangedReceivers);
+        }
+        for (ResolveInfo resolveInfo : smsAppChangedReceivers) {
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            if (activityInfo == null) {
+                continue;
+            }
+            final String packageName = activityInfo.packageName;
+            final SmsApplicationData smsApplicationData = receivers.get(packageName);
+            if (DEBUG_MULTIUSER) {
+                Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" +
+                        packageName + " smsApplicationData: " + smsApplicationData +
+                        " activityInfo.name: " + activityInfo.name);
+            }
+            if (smsApplicationData != null) {
+                smsApplicationData.mSmsAppChangedReceiverClass = activityInfo.name;
+            }
+        }
+
+        // Update any existing entries with the external provider changed handler.
+        intent = new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE);
+        List<ResolveInfo> providerChangedReceivers =
+                packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" +
+                    providerChangedReceivers);
+        }
+        for (ResolveInfo resolveInfo : providerChangedReceivers) {
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            if (activityInfo == null) {
+                continue;
+            }
+            final String packageName = activityInfo.packageName;
+            final SmsApplicationData smsApplicationData = receivers.get(packageName);
+            if (DEBUG_MULTIUSER) {
+                Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" +
+                        packageName + " smsApplicationData: " + smsApplicationData +
+                        " activityInfo.name: " + activityInfo.name);
+            }
+            if (smsApplicationData != null) {
+                smsApplicationData.mProviderChangedReceiverClass = activityInfo.name;
+            }
+        }
+
+        // Update any existing entries with the sim full handler.
+        intent = new Intent(Intents.SIM_FULL_ACTION);
+        List<ResolveInfo> simFullReceivers =
+                packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers="
+                    + simFullReceivers);
+        }
+        for (ResolveInfo resolveInfo : simFullReceivers) {
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            if (activityInfo == null) {
+                continue;
+            }
+            final String packageName = activityInfo.packageName;
+            final SmsApplicationData smsApplicationData = receivers.get(packageName);
+            if (DEBUG_MULTIUSER) {
+                Log.i(LOG_TAG, "getApplicationCollectionInternal packageName="
+                        + packageName + " smsApplicationData: " + smsApplicationData
+                        + " activityInfo.name: " + activityInfo.name);
+            }
+            if (smsApplicationData != null) {
+                smsApplicationData.mSimFullReceiverClass = activityInfo.name;
+            }
+        }
+
+        // Remove any entries for which we did not find all required intents.
+        for (ResolveInfo resolveInfo : smsReceivers) {
+            final ActivityInfo activityInfo = resolveInfo.activityInfo;
+            if (activityInfo == null) {
+                continue;
+            }
+            final String packageName = activityInfo.packageName;
+            final SmsApplicationData smsApplicationData = receivers.get(packageName);
+            if (smsApplicationData != null) {
+                if (!smsApplicationData.isComplete()) {
+                    receivers.remove(packageName);
+                }
+            }
+        }
+        return receivers.values();
+    }
+
+    /**
+     * Checks to see if we have a valid installed SMS application for the specified package name
+     * @return Data for the specified package name or null if there isn't one
+     */
+    private static SmsApplicationData getApplicationForPackage(
+            Collection<SmsApplicationData> applications, String packageName) {
+        if (packageName == null) {
+            return null;
+        }
+        // Is there an entry in the application list for the specified package?
+        for (SmsApplicationData application : applications) {
+            if (application.mPackageName.contentEquals(packageName)) {
+                return application;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the application we will use for delivering SMS/MMS messages.
+     *
+     * We return the preferred sms application with the following order of preference:
+     * (1) User selected SMS app (if selected, and if still valid)
+     * (2) Android Messaging (if installed)
+     * (3) The currently configured highest priority broadcast receiver
+     * (4) Null
+     */
+    private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded,
+            int userId) {
+        TelephonyManager tm = (TelephonyManager)
+                context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (!tm.isSmsCapable()) {
+            // No phone, no SMS
+            return null;
+        }
+
+        Collection<SmsApplicationData> applications = getApplicationCollectionInternal(context,
+                userId);
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "getApplication userId=" + userId);
+        }
+        // Determine which application receives the broadcast
+        String defaultApplication = Settings.Secure.getStringForUser(context.getContentResolver(),
+                Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication);
+        }
+
+        SmsApplicationData applicationData = null;
+        if (defaultApplication != null) {
+            applicationData = getApplicationForPackage(applications, defaultApplication);
+        }
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "getApplication appData=" + applicationData);
+        }
+        // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
+        // this if the caller asked us to.
+        if (updateIfNeeded && applicationData == null) {
+            // Try to find the default SMS package for this device
+            Resources r = context.getResources();
+            String defaultPackage =
+                    r.getString(com.android.internal.R.string.default_sms_application);
+            applicationData = getApplicationForPackage(applications, defaultPackage);
+
+            if (applicationData == null) {
+                // Are there any applications?
+                if (applications.size() != 0) {
+                    applicationData = (SmsApplicationData)applications.toArray()[0];
+                }
+            }
+
+            // If we found a new default app, update the setting
+            if (applicationData != null) {
+                setDefaultApplicationInternal(applicationData.mPackageName, context, userId);
+            }
+        }
+
+        // If we found a package, make sure AppOps permissions are set up correctly
+        if (applicationData != null) {
+            AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+
+            // We can only call checkOp if we are privileged (updateIfNeeded) or if the app we
+            // are checking is for our current uid. Doing this check from the unprivileged current
+            // SMS app allows us to tell the current SMS app that it is not in a good state and
+            // needs to ask to be the current SMS app again to work properly.
+            if (updateIfNeeded || applicationData.mUid == android.os.Process.myUid()) {
+                // Verify that the SMS app has permissions
+                int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
+                        applicationData.mPackageName);
+                if (mode != AppOpsManager.MODE_ALLOWED) {
+                    Rlog.e(LOG_TAG, applicationData.mPackageName + " lost OP_WRITE_SMS: " +
+                            (updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
+                    if (updateIfNeeded) {
+                        appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
+                                applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
+                    } else {
+                        // We can not return a package if permissions are not set up correctly
+                        applicationData = null;
+                    }
+                }
+            }
+
+            // We can only verify the phone and BT app's permissions from a privileged caller
+            if (updateIfNeeded) {
+                // Ensure this component is still configured as the preferred activity. Usually the
+                // current SMS app will already be the preferred activity - but checking whether or
+                // not this is true is just as expensive as reconfiguring the preferred activity so
+                // we just reconfigure every time.
+                PackageManager packageManager = context.getPackageManager();
+                configurePreferredActivity(packageManager, new ComponentName(
+                        applicationData.mPackageName, applicationData.mSendToClass),
+                        userId);
+                // Assign permission to special system apps
+                assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+                        PHONE_PACKAGE_NAME);
+                assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+                        BLUETOOTH_PACKAGE_NAME);
+                assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+                        MMS_SERVICE_PACKAGE_NAME);
+                assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+                        TELEPHONY_PROVIDER_PACKAGE_NAME);
+                // Give WRITE_SMS AppOps permission to UID 1001 which contains multiple
+                // apps, all of them should be able to write to telephony provider.
+                // This is to allow the proxy package permission check in telephony provider
+                // to pass.
+                assignWriteSmsPermissionToSystemUid(appOps, Process.PHONE_UID);
+            }
+        }
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "getApplication returning appData=" + applicationData);
+        }
+        return applicationData;
+    }
+
+    /**
+     * Sets the specified package as the default SMS/MMS application. The caller of this method
+     * needs to have permission to set AppOps and write to secure settings.
+     */
+    public static void setDefaultApplication(String packageName, Context context) {
+        TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (!tm.isSmsCapable()) {
+            // No phone, no SMS
+            return;
+        }
+
+        final int userId = getIncomingUserId(context);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            setDefaultApplicationInternal(packageName, context, userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private static void setDefaultApplicationInternal(String packageName, Context context,
+            int userId) {
+        // Get old package name
+        String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
+                Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+
+        if (DEBUG_MULTIUSER) {
+            Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldPackageName +
+                    " new=" + packageName);
+        }
+
+        if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) {
+            // No change
+            return;
+        }
+
+        // We only make the change if the new package is valid
+        PackageManager packageManager = context.getPackageManager();
+        Collection<SmsApplicationData> applications = getApplicationCollection(context);
+        SmsApplicationData oldAppData = oldPackageName != null ?
+                getApplicationForPackage(applications, oldPackageName) : null;
+        SmsApplicationData applicationData = getApplicationForPackage(applications, packageName);
+        if (applicationData != null) {
+            // Ignore OP_WRITE_SMS for the previously configured default SMS app.
+            AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+            if (oldPackageName != null) {
+                try {
+                    PackageInfo info = packageManager.getPackageInfo(oldPackageName,
+                            PackageManager.GET_UNINSTALLED_PACKAGES);
+                    appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+                            oldPackageName, AppOpsManager.MODE_IGNORED);
+                } catch (NameNotFoundException e) {
+                    Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
+                }
+            }
+
+            // Update the secure setting.
+            Settings.Secure.putStringForUser(context.getContentResolver(),
+                    Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName,
+                    userId);
+
+            // Configure this as the preferred activity for SENDTO sms/mms intents
+            configurePreferredActivity(packageManager, new ComponentName(
+                    applicationData.mPackageName, applicationData.mSendToClass), userId);
+
+            // Allow OP_WRITE_SMS for the newly configured default SMS app.
+            appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
+                    applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
+
+            // Assign permission to special system apps
+            assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+                    PHONE_PACKAGE_NAME);
+            assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+                    BLUETOOTH_PACKAGE_NAME);
+            assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+                    MMS_SERVICE_PACKAGE_NAME);
+            assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+                    TELEPHONY_PROVIDER_PACKAGE_NAME);
+            // Give WRITE_SMS AppOps permission to UID 1001 which contains multiple
+            // apps, all of them should be able to write to telephony provider.
+            // This is to allow the proxy package permission check in telephony provider
+            // to pass.
+            assignWriteSmsPermissionToSystemUid(appOps, Process.PHONE_UID);
+
+            if (DEBUG_MULTIUSER) {
+                Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData);
+            }
+            if (oldAppData != null && oldAppData.mSmsAppChangedReceiverClass != null) {
+                // Notify the old sms app that it's no longer the default
+                final Intent oldAppIntent =
+                        new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+                final ComponentName component = new ComponentName(oldAppData.mPackageName,
+                        oldAppData.mSmsAppChangedReceiverClass);
+                oldAppIntent.setComponent(component);
+                oldAppIntent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, false);
+                if (DEBUG_MULTIUSER) {
+                    Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldAppData.mPackageName);
+                }
+                context.sendBroadcast(oldAppIntent);
+            }
+            // Notify the new sms app that it's now the default (if the new sms app has a receiver
+            // to handle the changed default sms intent).
+            if (DEBUG_MULTIUSER) {
+                Log.i(LOG_TAG, "setDefaultApplicationInternal new applicationData=" +
+                        applicationData);
+            }
+            if (applicationData.mSmsAppChangedReceiverClass != null) {
+                final Intent intent =
+                        new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+                final ComponentName component = new ComponentName(applicationData.mPackageName,
+                        applicationData.mSmsAppChangedReceiverClass);
+                intent.setComponent(component);
+                intent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, true);
+                if (DEBUG_MULTIUSER) {
+                    Log.i(LOG_TAG, "setDefaultApplicationInternal new=" + packageName);
+                }
+                context.sendBroadcast(intent);
+            }
+            MetricsLogger.action(context, MetricsEvent.ACTION_DEFAULT_SMS_APP_CHANGED,
+                    applicationData.mPackageName);
+        }
+    }
+
+    /**
+     * Assign WRITE_SMS AppOps permission to some special system apps.
+     *
+     * @param context The context
+     * @param packageManager The package manager instance
+     * @param appOps The AppOps manager instance
+     * @param packageName The package name of the system app
+     */
+    private static void assignWriteSmsPermissionToSystemApp(Context context,
+            PackageManager packageManager, AppOpsManager appOps, String packageName) {
+        // First check package signature matches the caller's package signature.
+        // Since this class is only used internally by the system, this check makes sure
+        // the package signature matches system signature.
+        final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
+        if (result != PackageManager.SIGNATURE_MATCH) {
+            Rlog.e(LOG_TAG, packageName + " does not have system signature");
+            return;
+        }
+        try {
+            PackageInfo info = packageManager.getPackageInfo(packageName, 0);
+            int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+                    packageName);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS:  (fixing)");
+                appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+                        packageName, AppOpsManager.MODE_ALLOWED);
+            }
+        } catch (NameNotFoundException e) {
+            // No whitelisted system app on this device
+            Rlog.e(LOG_TAG, "Package not found: " + packageName);
+        }
+
+    }
+
+    private static void assignWriteSmsPermissionToSystemUid(AppOpsManager appOps, int uid) {
+        appOps.setUidMode(AppOpsManager.OP_WRITE_SMS, uid, AppOpsManager.MODE_ALLOWED);
+    }
+
+    /**
+     * Tracks package changes and ensures that the default SMS app is always configured to be the
+     * preferred activity for SENDTO sms/mms intents.
+     */
+    private static final class SmsPackageMonitor extends PackageMonitor {
+        final Context mContext;
+
+        public SmsPackageMonitor(Context context) {
+            super();
+            mContext = context;
+        }
+
+        @Override
+        public void onPackageDisappeared(String packageName, int reason) {
+            onPackageChanged();
+        }
+
+        @Override
+        public void onPackageAppeared(String packageName, int reason) {
+            onPackageChanged();
+        }
+
+        @Override
+        public void onPackageModified(String packageName) {
+            onPackageChanged();
+        }
+
+        private void onPackageChanged() {
+            PackageManager packageManager = mContext.getPackageManager();
+            Context userContext = mContext;
+            final int userId = getSendingUserId();
+            if (userId != UserHandle.USER_SYSTEM) {
+                try {
+                    userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
+                            new UserHandle(userId));
+                } catch (NameNotFoundException nnfe) {
+                    if (DEBUG_MULTIUSER) {
+                        Log.w(LOG_TAG, "Unable to create package context for user " + userId);
+                    }
+                }
+            }
+            // Ensure this component is still configured as the preferred activity
+            ComponentName componentName = getDefaultSendToApplication(userContext, true);
+            if (componentName != null) {
+                configurePreferredActivity(packageManager, componentName, userId);
+            }
+        }
+    }
+
+    public static void initSmsPackageMonitor(Context context) {
+        sSmsPackageMonitor = new SmsPackageMonitor(context);
+        sSmsPackageMonitor.register(context, context.getMainLooper(), UserHandle.ALL, false);
+    }
+
+    private static void configurePreferredActivity(PackageManager packageManager,
+            ComponentName componentName, int userId) {
+        // Add the four activity preferences we want to direct to this app.
+        replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMS);
+        replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMSTO);
+        replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMS);
+        replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMSTO);
+    }
+
+    /**
+     * Updates the ACTION_SENDTO activity to the specified component for the specified scheme.
+     */
+    private static void replacePreferredActivity(PackageManager packageManager,
+            ComponentName componentName, int userId, String scheme) {
+        // Build the set of existing activities that handle this scheme
+        Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null));
+        List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivitiesAsUser(
+                intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER,
+                userId);
+
+        // Build the set of ComponentNames for these activities
+        final int n = resolveInfoList.size();
+        ComponentName[] set = new ComponentName[n];
+        for (int i = 0; i < n; i++) {
+            ResolveInfo info = resolveInfoList.get(i);
+            set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+        }
+
+        // Update the preferred SENDTO activity for the specified scheme
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SENDTO);
+        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+        intentFilter.addDataScheme(scheme);
+        packageManager.replacePreferredActivityAsUser(intentFilter,
+                IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
+                set, componentName, userId);
+    }
+
+    /**
+     * Returns SmsApplicationData for this package if this package is capable of being set as the
+     * default SMS application.
+     */
+    public static SmsApplicationData getSmsApplicationData(String packageName, Context context) {
+        Collection<SmsApplicationData> applications = getApplicationCollection(context);
+        return getApplicationForPackage(applications, packageName);
+    }
+
+    /**
+     * Gets the default SMS application
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured.
+     * @return component name of the app and class to deliver SMS messages to
+     */
+    public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
+        int userId = getIncomingUserId(context);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            ComponentName component = null;
+            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+                    userId);
+            if (smsApplicationData != null) {
+                component = new ComponentName(smsApplicationData.mPackageName,
+                        smsApplicationData.mSmsReceiverClass);
+            }
+            return component;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Gets the default MMS application
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured.
+     * @return component name of the app and class to deliver MMS messages to
+     */
+    public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
+        int userId = getIncomingUserId(context);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            ComponentName component = null;
+            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+                    userId);
+            if (smsApplicationData != null) {
+                component = new ComponentName(smsApplicationData.mPackageName,
+                        smsApplicationData.mMmsReceiverClass);
+            }
+            return component;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Gets the default Respond Via Message application
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured.
+     * @return component name of the app and class to direct Respond Via Message intent to
+     */
+    public static ComponentName getDefaultRespondViaMessageApplication(Context context,
+            boolean updateIfNeeded) {
+        int userId = getIncomingUserId(context);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            ComponentName component = null;
+            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+                    userId);
+            if (smsApplicationData != null) {
+                component = new ComponentName(smsApplicationData.mPackageName,
+                        smsApplicationData.mRespondViaMessageClass);
+            }
+            return component;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Gets the default Send To (smsto) application.
+     * <p>
+     * Caller must pass in the correct user context if calling from a singleton service.
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured.
+     * @return component name of the app and class to direct SEND_TO (smsto) intent to
+     */
+    public static ComponentName getDefaultSendToApplication(Context context,
+            boolean updateIfNeeded) {
+        int userId = getIncomingUserId(context);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            ComponentName component = null;
+            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+                    userId);
+            if (smsApplicationData != null) {
+                component = new ComponentName(smsApplicationData.mPackageName,
+                        smsApplicationData.mSendToClass);
+            }
+            return component;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Gets the default application that handles external changes to the SmsProvider and
+     * MmsProvider.
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured.
+     * @return component name of the app and class to deliver change intents to
+     */
+    public static ComponentName getDefaultExternalTelephonyProviderChangedApplication(
+            Context context, boolean updateIfNeeded) {
+        int userId = getIncomingUserId(context);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            ComponentName component = null;
+            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+                    userId);
+            if (smsApplicationData != null
+                    && smsApplicationData.mProviderChangedReceiverClass != null) {
+                component = new ComponentName(smsApplicationData.mPackageName,
+                        smsApplicationData.mProviderChangedReceiverClass);
+            }
+            return component;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Gets the default application that handles sim full event.
+     * @param context context from the calling app
+     * @param updateIfNeeded update the default app if there is no valid default app configured.
+     * @return component name of the app and class to deliver change intents to
+     */
+    public static ComponentName getDefaultSimFullApplication(
+            Context context, boolean updateIfNeeded) {
+        int userId = getIncomingUserId(context);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            ComponentName component = null;
+            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+                    userId);
+            if (smsApplicationData != null
+                    && smsApplicationData.mSimFullReceiverClass != null) {
+                component = new ComponentName(smsApplicationData.mPackageName,
+                        smsApplicationData.mSimFullReceiverClass);
+            }
+            return component;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Returns whether need to write the SMS message to SMS database for this package.
+     * <p>
+     * Caller must pass in the correct user context if calling from a singleton service.
+     */
+    public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
+        if (SmsManager.getDefault().getAutoPersisting()) {
+            return true;
+        }
+        return !isDefaultSmsApplication(context, packageName);
+    }
+
+    /**
+     * Check if a package is default sms app (or equivalent, like bluetooth)
+     *
+     * @param context context from the calling app
+     * @param packageName the name of the package to be checked
+     * @return true if the package is default sms app or bluetooth
+     */
+    public static boolean isDefaultSmsApplication(Context context, String packageName) {
+        if (packageName == null) {
+            return false;
+        }
+        final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context);
+        if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName))
+                || BLUETOOTH_PACKAGE_NAME.equals(packageName)) {
+            return true;
+        }
+        return false;
+    }
+
+    private static String getDefaultSmsApplicationPackageName(Context context) {
+        final ComponentName component = getDefaultSmsApplication(context, false);
+        if (component != null) {
+            return component.getPackageName();
+        }
+        return null;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java b/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
new file mode 100644
index 0000000..c912924
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}.
+ * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and
+ * 3GPP TS 23.041 (for GSM/UMTS).
+ *
+ * {@hide}
+ */
+public class SmsCbCmasInfo implements Parcelable {
+
+    // CMAS message class (in GSM/UMTS message identifier or CDMA service category).
+
+    /** Presidential-level alert (Korean Public Alert System Class 0 message). */
+    public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00;
+
+    /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */
+    public static final int CMAS_CLASS_EXTREME_THREAT = 0x01;
+
+    /** Severe threat to life and property (Korean Public Alert System Class 1 message). */
+    public static final int CMAS_CLASS_SEVERE_THREAT = 0x02;
+
+    /** Child abduction emergency (AMBER Alert). */
+    public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03;
+
+    /** CMAS test message. */
+    public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04;
+
+    /** CMAS exercise. */
+    public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05;
+
+    /** CMAS category for operator defined use. */
+    public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06;
+
+    /** CMAS category for warning types that are reserved for future extension. */
+    public static final int CMAS_CLASS_UNKNOWN = -1;
+
+    // CMAS alert category (in CDMA type 1 elements record).
+
+    /** CMAS alert category: Geophysical including landslide. */
+    public static final int CMAS_CATEGORY_GEO = 0x00;
+
+    /** CMAS alert category: Meteorological including flood. */
+    public static final int CMAS_CATEGORY_MET = 0x01;
+
+    /** CMAS alert category: General emergency and public safety. */
+    public static final int CMAS_CATEGORY_SAFETY = 0x02;
+
+    /** CMAS alert category: Law enforcement, military, homeland/local/private security. */
+    public static final int CMAS_CATEGORY_SECURITY = 0x03;
+
+    /** CMAS alert category: Rescue and recovery. */
+    public static final int CMAS_CATEGORY_RESCUE = 0x04;
+
+    /** CMAS alert category: Fire suppression and rescue. */
+    public static final int CMAS_CATEGORY_FIRE = 0x05;
+
+    /** CMAS alert category: Medical and public health. */
+    public static final int CMAS_CATEGORY_HEALTH = 0x06;
+
+    /** CMAS alert category: Pollution and other environmental. */
+    public static final int CMAS_CATEGORY_ENV = 0x07;
+
+    /** CMAS alert category: Public and private transportation. */
+    public static final int CMAS_CATEGORY_TRANSPORT = 0x08;
+
+    /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */
+    public static final int CMAS_CATEGORY_INFRA = 0x09;
+
+    /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */
+    public static final int CMAS_CATEGORY_CBRNE = 0x0a;
+
+    /** CMAS alert category: Other events. */
+    public static final int CMAS_CATEGORY_OTHER = 0x0b;
+
+    /**
+     * CMAS alert category is unknown. The category is only available for CDMA broadcasts
+     * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+     */
+    public static final int CMAS_CATEGORY_UNKNOWN = -1;
+
+    // CMAS response type (in CDMA type 1 elements record).
+
+    /** CMAS response type: Take shelter in place. */
+    public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00;
+
+    /** CMAS response type: Evacuate (Relocate). */
+    public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01;
+
+    /** CMAS response type: Make preparations. */
+    public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02;
+
+    /** CMAS response type: Execute a pre-planned activity. */
+    public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03;
+
+    /** CMAS response type: Attend to information sources. */
+    public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04;
+
+    /** CMAS response type: Avoid hazard. */
+    public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05;
+
+    /** CMAS response type: Evaluate the information in this message (not for public warnings). */
+    public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06;
+
+    /** CMAS response type: No action recommended. */
+    public static final int CMAS_RESPONSE_TYPE_NONE = 0x07;
+
+    /**
+     * CMAS response type is unknown. The response type is only available for CDMA broadcasts
+     * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+     */
+    public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1;
+
+    // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+    /** CMAS severity type: Extraordinary threat to life or property. */
+    public static final int CMAS_SEVERITY_EXTREME = 0x0;
+
+    /** CMAS severity type: Significant threat to life or property. */
+    public static final int CMAS_SEVERITY_SEVERE = 0x1;
+
+    /**
+     * CMAS alert severity is unknown. The severity is available for CDMA warning alerts
+     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+     * Presidential-level alert class (Korean Public Alert System Class 0).
+     */
+    public static final int CMAS_SEVERITY_UNKNOWN = -1;
+
+    // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+    /** CMAS urgency type: Responsive action should be taken immediately. */
+    public static final int CMAS_URGENCY_IMMEDIATE = 0x0;
+
+    /** CMAS urgency type: Responsive action should be taken within the next hour. */
+    public static final int CMAS_URGENCY_EXPECTED = 0x1;
+
+    /**
+     * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts
+     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+     * Presidential-level alert class (Korean Public Alert System Class 0).
+     */
+    public static final int CMAS_URGENCY_UNKNOWN = -1;
+
+    // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+    /** CMAS certainty type: Determined to have occurred or to be ongoing. */
+    public static final int CMAS_CERTAINTY_OBSERVED = 0x0;
+
+    /** CMAS certainty type: Likely (probability > ~50%). */
+    public static final int CMAS_CERTAINTY_LIKELY = 0x1;
+
+    /**
+     * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts
+     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+     * Presidential-level alert class (Korean Public Alert System Class 0).
+     */
+    public static final int CMAS_CERTAINTY_UNKNOWN = -1;
+
+    /** CMAS message class. */
+    private final int mMessageClass;
+
+    /** CMAS category. */
+    private final int mCategory;
+
+    /** CMAS response type. */
+    private final int mResponseType;
+
+    /** CMAS severity. */
+    private final int mSeverity;
+
+    /** CMAS urgency. */
+    private final int mUrgency;
+
+    /** CMAS certainty. */
+    private final int mCertainty;
+
+    /** Create a new SmsCbCmasInfo object with the specified values. */
+    public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity,
+            int urgency, int certainty) {
+        mMessageClass = messageClass;
+        mCategory = category;
+        mResponseType = responseType;
+        mSeverity = severity;
+        mUrgency = urgency;
+        mCertainty = certainty;
+    }
+
+    /** Create a new SmsCbCmasInfo object from a Parcel. */
+    SmsCbCmasInfo(Parcel in) {
+        mMessageClass = in.readInt();
+        mCategory = in.readInt();
+        mResponseType = in.readInt();
+        mSeverity = in.readInt();
+        mUrgency = in.readInt();
+        mCertainty = in.readInt();
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMessageClass);
+        dest.writeInt(mCategory);
+        dest.writeInt(mResponseType);
+        dest.writeInt(mSeverity);
+        dest.writeInt(mUrgency);
+        dest.writeInt(mCertainty);
+    }
+
+    /**
+     * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}.
+     * @return one of the {@code CMAS_CLASS} values
+     */
+    public int getMessageClass() {
+        return mMessageClass;
+    }
+
+    /**
+     * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}.
+     * @return one of the {@code CMAS_CATEGORY} values
+     */
+    public int getCategory() {
+        return mCategory;
+    }
+
+    /**
+     * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}.
+     * @return one of the {@code CMAS_RESPONSE_TYPE} values
+     */
+    public int getResponseType() {
+        return mResponseType;
+    }
+
+    /**
+     * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}.
+     * @return one of the {@code CMAS_SEVERITY} values
+     */
+    public int getSeverity() {
+        return mSeverity;
+    }
+
+    /**
+     * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}.
+     * @return one of the {@code CMAS_URGENCY} values
+     */
+    public int getUrgency() {
+        return mUrgency;
+    }
+
+    /**
+     * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}.
+     * @return one of the {@code CMAS_CERTAINTY} values
+     */
+    public int getCertainty() {
+        return mCertainty;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory
+                + ", responseType=" + mResponseType + ", severity=" + mSeverity
+                + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Creator for unparcelling objects. */
+    public static final Parcelable.Creator<SmsCbCmasInfo>
+            CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() {
+        @Override
+        public SmsCbCmasInfo createFromParcel(Parcel in) {
+            return new SmsCbCmasInfo(in);
+        }
+
+        @Override
+        public SmsCbCmasInfo[] newArray(int size) {
+            return new SmsCbCmasInfo[size];
+        }
+    };
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java b/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
new file mode 100644
index 0000000..14e02de
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.format.Time;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+import java.util.Arrays;
+
+/**
+ * Contains information elements for a GSM or UMTS ETWS warning notification.
+ * Supported values for each element are defined in 3GPP TS 23.041.
+ *
+ * {@hide}
+ */
+public class SmsCbEtwsInfo implements Parcelable {
+
+    /** ETWS warning type for earthquake. */
+    public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
+
+    /** ETWS warning type for tsunami. */
+    public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01;
+
+    /** ETWS warning type for earthquake and tsunami. */
+    public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02;
+
+    /** ETWS warning type for test messages. */
+    public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 0x03;
+
+    /** ETWS warning type for other emergency types. */
+    public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 0x04;
+
+    /** Unknown ETWS warning type. */
+    public static final int ETWS_WARNING_TYPE_UNKNOWN = -1;
+
+    /** One of the ETWS warning type constants defined in this class. */
+    private final int mWarningType;
+
+    /** Whether or not to activate the emergency user alert tone and vibration. */
+    private final boolean mEmergencyUserAlert;
+
+    /** Whether or not to activate a popup alert. */
+    private final boolean mActivatePopup;
+
+    /** Whether ETWS primary message or not/ */
+    private final boolean mPrimary;
+
+    /**
+     * 50-byte security information (ETWS primary notification for GSM only). As of Release 10,
+     * 3GPP TS 23.041 states that the UE shall ignore the ETWS primary notification timestamp
+     * and digital signature if received. Therefore it is treated as a raw byte array and
+     * parceled with the broadcast intent if present, but the timestamp is only computed if an
+     * application asks for the individual components.
+     */
+    private final byte[] mWarningSecurityInformation;
+
+    /** Create a new SmsCbEtwsInfo object with the specified values. */
+    public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup,
+                boolean primary, byte[] warningSecurityInformation) {
+        mWarningType = warningType;
+        mEmergencyUserAlert = emergencyUserAlert;
+        mActivatePopup = activatePopup;
+        mPrimary = primary;
+        mWarningSecurityInformation = warningSecurityInformation;
+    }
+
+    /** Create a new SmsCbEtwsInfo object from a Parcel. */
+    SmsCbEtwsInfo(Parcel in) {
+        mWarningType = in.readInt();
+        mEmergencyUserAlert = (in.readInt() != 0);
+        mActivatePopup = (in.readInt() != 0);
+        mPrimary = (in.readInt() != 0);
+        mWarningSecurityInformation = in.createByteArray();
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mWarningType);
+        dest.writeInt(mEmergencyUserAlert ? 1 : 0);
+        dest.writeInt(mActivatePopup ? 1 : 0);
+        dest.writeInt(mPrimary ? 1 : 0);
+        dest.writeByteArray(mWarningSecurityInformation);
+    }
+
+    /**
+     * Returns the ETWS warning type.
+     * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE}
+     */
+    public int getWarningType() {
+        return mWarningType;
+    }
+
+    /**
+     * Returns the ETWS emergency user alert flag.
+     * @return true to notify terminal to activate emergency user alert; false otherwise
+     */
+    public boolean isEmergencyUserAlert() {
+        return mEmergencyUserAlert;
+    }
+
+    /**
+     * Returns the ETWS activate popup flag.
+     * @return true to notify terminal to activate display popup; false otherwise
+     */
+    public boolean isPopupAlert() {
+        return mActivatePopup;
+    }
+
+    /**
+     * Returns the ETWS format flag.
+     * @return true if the message is primary message, otherwise secondary message
+     */
+    public boolean isPrimary() {
+        return mPrimary;
+    }
+
+    /**
+     * Returns the Warning-Security-Information timestamp (GSM primary notifications only).
+     * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received.
+     * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present
+     */
+    public long getPrimaryNotificationTimestamp() {
+        if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) {
+            return 0;
+        }
+
+        int year = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[0]);
+        int month = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[1]);
+        int day = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[2]);
+        int hour = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[3]);
+        int minute = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[4]);
+        int second = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[5]);
+
+        // For the timezone, the most significant bit of the
+        // least significant nibble is the sign byte
+        // (meaning the max range of this field is 79 quarter-hours,
+        // which is more than enough)
+
+        byte tzByte = mWarningSecurityInformation[6];
+
+        // Mask out sign bit.
+        int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
+
+        timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+
+        Time time = new Time(Time.TIMEZONE_UTC);
+
+        // We only need to support years above 2000.
+        time.year = year + 2000;
+        time.month = month - 1;
+        time.monthDay = day;
+        time.hour = hour;
+        time.minute = minute;
+        time.second = second;
+
+        // Timezone offset is in quarter hours.
+        return time.toMillis(true) - timezoneOffset * 15 * 60 * 1000;
+    }
+
+    /**
+     * Returns the digital signature (GSM primary notifications only). As of Release 10,
+     * 3GPP TS 23.041 states that the UE shall ignore this value if received.
+     * @return a byte array containing a copy of the primary notification digital signature
+     */
+    public byte[] getPrimaryNotificationSignature() {
+        if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) {
+            return null;
+        }
+        return Arrays.copyOfRange(mWarningSecurityInformation, 7, 50);
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert="
+                + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Creator for unparcelling objects. */
+    public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() {
+        @Override
+        public SmsCbEtwsInfo createFromParcel(Parcel in) {
+            return new SmsCbEtwsInfo(in);
+        }
+
+        @Override
+        public SmsCbEtwsInfo[] newArray(int size) {
+            return new SmsCbEtwsInfo[size];
+        }
+    };
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbLocation.java b/telephony/java/com/android/internal/telephony/SmsCbLocation.java
new file mode 100644
index 0000000..6eb72a8
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbLocation.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents the location and geographical scope of a cell broadcast message.
+ * For GSM/UMTS, the Location Area and Cell ID are set when the broadcast
+ * geographical scope is cell wide or Location Area wide. For CDMA, the
+ * broadcast geographical scope is always PLMN wide.
+ *
+ * @hide
+ */
+public class SmsCbLocation implements Parcelable {
+
+    /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */
+    private final String mPlmn;
+
+    private final int mLac;
+    private final int mCid;
+
+    /**
+     * Construct an empty location object. This is used for some test cases, and for
+     * cell broadcasts saved in older versions of the database without location info.
+     */
+    public SmsCbLocation() {
+        mPlmn = "";
+        mLac = -1;
+        mCid = -1;
+    }
+
+    /**
+     * Construct a location object for the PLMN. This class is immutable, so
+     * the same object can be reused for multiple broadcasts.
+     */
+    public SmsCbLocation(String plmn) {
+        mPlmn = plmn;
+        mLac = -1;
+        mCid = -1;
+    }
+
+    /**
+     * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so
+     * the same object can be reused for multiple broadcasts.
+     */
+    public SmsCbLocation(String plmn, int lac, int cid) {
+        mPlmn = plmn;
+        mLac = lac;
+        mCid = cid;
+    }
+
+    /**
+     * Initialize the object from a Parcel.
+     */
+    public SmsCbLocation(Parcel in) {
+        mPlmn = in.readString();
+        mLac = in.readInt();
+        mCid = in.readInt();
+    }
+
+    /**
+     * Returns the MCC/MNC of the network as a String.
+     * @return the PLMN identifier (MCC+MNC) as a String
+     */
+    public String getPlmn() {
+        return mPlmn;
+    }
+
+    /**
+     * Returns the GSM location area code, or UMTS service area code.
+     * @return location area code, -1 if unknown, 0xffff max legal value
+     */
+    public int getLac() {
+        return mLac;
+    }
+
+    /**
+     * Returns the GSM or UMTS cell ID.
+     * @return gsm cell id, -1 if unknown, 0xffff max legal value
+     */
+    public int getCid() {
+        return mCid;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = mPlmn.hashCode();
+        hash = hash * 31 + mLac;
+        hash = hash * 31 + mCid;
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || !(o instanceof SmsCbLocation)) {
+            return false;
+        }
+        SmsCbLocation other = (SmsCbLocation) o;
+        return mPlmn.equals(other.mPlmn) && mLac == other.mLac && mCid == other.mCid;
+    }
+
+    @Override
+    public String toString() {
+        return '[' + mPlmn + ',' + mLac + ',' + mCid + ']';
+    }
+
+    /**
+     * Test whether this location is within the location area of the specified object.
+     *
+     * @param area the location area to compare with this location
+     * @return true if this location is contained within the specified location area
+     */
+    public boolean isInLocationArea(SmsCbLocation area) {
+        if (mCid != -1 && mCid != area.mCid) {
+            return false;
+        }
+        if (mLac != -1 && mLac != area.mLac) {
+            return false;
+        }
+        return mPlmn.equals(area.mPlmn);
+    }
+
+    /**
+     * Test whether this location is within the location area of the CellLocation.
+     *
+     * @param plmn the PLMN to use for comparison
+     * @param lac the Location Area (GSM) or Service Area (UMTS) to compare with
+     * @param cid the Cell ID to compare with
+     * @return true if this location is contained within the specified PLMN, LAC, and Cell ID
+     */
+    public boolean isInLocationArea(String plmn, int lac, int cid) {
+        if (!mPlmn.equals(plmn)) {
+            return false;
+        }
+
+        if (mLac != -1 && mLac != lac) {
+            return false;
+        }
+
+        if (mCid != -1 && mCid != cid) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mPlmn);
+        dest.writeInt(mLac);
+        dest.writeInt(mCid);
+    }
+
+    public static final Parcelable.Creator<SmsCbLocation> CREATOR
+            = new Parcelable.Creator<SmsCbLocation>() {
+        @Override
+        public SmsCbLocation createFromParcel(Parcel in) {
+            return new SmsCbLocation(in);
+        }
+
+        @Override
+        public SmsCbLocation[] newArray(int size) {
+            return new SmsCbLocation[size];
+        }
+    };
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbMessage.java b/telephony/java/com/android/internal/telephony/SmsCbMessage.java
new file mode 100644
index 0000000..046bf8c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbMessage.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2010 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable object containing a received cell broadcast message. There are four different types
+ * of Cell Broadcast messages:
+ *
+ * <ul>
+ * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
+ * <li>cell information messages, broadcast on channel 50, indicating the current cell name for
+ *  roaming purposes (required to display on the idle screen in Brazil)</li>
+ * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
+ * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
+ * </ul>
+ *
+ * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
+ * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
+ * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
+ * two completely different concepts in 3GPP and CDMA.
+ *
+ * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
+ * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
+ * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
+ * application should
+ *
+ * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
+ * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
+ * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
+ * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
+ * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
+ * Service Area in UMTS). The relevant values are concatenated into a single String which will be
+ * unique if the messages are not duplicates.
+ *
+ * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
+ * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
+ *
+ * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
+ * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
+ * Only system applications such as the CellBroadcastReceiver may receive notifications for
+ * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
+ * interference with the immediate display of the alert message and playing of the alert sound and
+ * vibration pattern, which could be caused by poorly written or malicious non-system code.
+ *
+ * @hide
+ */
+public class SmsCbMessage implements Parcelable {
+
+    protected static final String LOG_TAG = "SMSCB";
+
+    /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
+    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
+
+    /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
+    public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
+
+    /** Location / service area wide geographical scope (GSM/UMTS only). */
+    public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
+
+    /** Cell wide geographical scope (GSM/UMTS only). */
+    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
+
+    /** GSM or UMTS format cell broadcast. */
+    public static final int MESSAGE_FORMAT_3GPP = 1;
+
+    /** CDMA format cell broadcast. */
+    public static final int MESSAGE_FORMAT_3GPP2 = 2;
+
+    /** Normal message priority. */
+    public static final int MESSAGE_PRIORITY_NORMAL = 0;
+
+    /** Interactive message priority. */
+    public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
+
+    /** Urgent message priority. */
+    public static final int MESSAGE_PRIORITY_URGENT = 2;
+
+    /** Emergency message priority. */
+    public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
+
+    /** Format of this message (for interpretation of service category values). */
+    private final int mMessageFormat;
+
+    /** Geographical scope of broadcast. */
+    private final int mGeographicalScope;
+
+    /**
+     * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
+     * update number for GSM/UMTS). The serial number plus the location code uniquely identify
+     * a cell broadcast for duplicate detection.
+     */
+    private final int mSerialNumber;
+
+    /**
+     * Location identifier for this message. It consists of the current operator MCC/MNC as a
+     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+     * message is not binary 01, the Location Area is included for comparison. If the GS is
+     * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
+     */
+    private final SmsCbLocation mLocation;
+
+    /**
+     * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
+     * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
+     * or {@link #getCmasWarningInfo()}.
+     */
+    private final int mServiceCategory;
+
+    /** Message language, as a two-character string, e.g. "en". */
+    private final String mLanguage;
+
+    /** Message body, as a String. */
+    private final String mBody;
+
+    /** Message priority (including emergency priority). */
+    private final int mPriority;
+
+    /** ETWS warning notification information (ETWS warnings only). */
+    private final SmsCbEtwsInfo mEtwsWarningInfo;
+
+    /** CMAS warning notification information (CMAS warnings only). */
+    private final SmsCbCmasInfo mCmasWarningInfo;
+
+    /**
+     * Create a new SmsCbMessage with the specified data.
+     */
+    public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
+            SmsCbLocation location, int serviceCategory, String language, String body,
+            int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) {
+        mMessageFormat = messageFormat;
+        mGeographicalScope = geographicalScope;
+        mSerialNumber = serialNumber;
+        mLocation = location;
+        mServiceCategory = serviceCategory;
+        mLanguage = language;
+        mBody = body;
+        mPriority = priority;
+        mEtwsWarningInfo = etwsWarningInfo;
+        mCmasWarningInfo = cmasWarningInfo;
+    }
+
+    /** Create a new SmsCbMessage object from a Parcel. */
+    public SmsCbMessage(Parcel in) {
+        mMessageFormat = in.readInt();
+        mGeographicalScope = in.readInt();
+        mSerialNumber = in.readInt();
+        mLocation = new SmsCbLocation(in);
+        mServiceCategory = in.readInt();
+        mLanguage = in.readString();
+        mBody = in.readString();
+        mPriority = in.readInt();
+        int type = in.readInt();
+        switch (type) {
+            case 'E':
+                // unparcel ETWS warning information
+                mEtwsWarningInfo = new SmsCbEtwsInfo(in);
+                mCmasWarningInfo = null;
+                break;
+
+            case 'C':
+                // unparcel CMAS warning information
+                mEtwsWarningInfo = null;
+                mCmasWarningInfo = new SmsCbCmasInfo(in);
+                break;
+
+            default:
+                mEtwsWarningInfo = null;
+                mCmasWarningInfo = null;
+        }
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMessageFormat);
+        dest.writeInt(mGeographicalScope);
+        dest.writeInt(mSerialNumber);
+        mLocation.writeToParcel(dest, flags);
+        dest.writeInt(mServiceCategory);
+        dest.writeString(mLanguage);
+        dest.writeString(mBody);
+        dest.writeInt(mPriority);
+        if (mEtwsWarningInfo != null) {
+            // parcel ETWS warning information
+            dest.writeInt('E');
+            mEtwsWarningInfo.writeToParcel(dest, flags);
+        } else if (mCmasWarningInfo != null) {
+            // parcel CMAS warning information
+            dest.writeInt('C');
+            mCmasWarningInfo.writeToParcel(dest, flags);
+        } else {
+            // no ETWS or CMAS warning information
+            dest.writeInt('0');
+        }
+    }
+
+    public static final Parcelable.Creator<SmsCbMessage> CREATOR
+            = new Parcelable.Creator<SmsCbMessage>() {
+        @Override
+        public SmsCbMessage createFromParcel(Parcel in) {
+            return new SmsCbMessage(in);
+        }
+
+        @Override
+        public SmsCbMessage[] newArray(int size) {
+            return new SmsCbMessage[size];
+        }
+    };
+
+    /**
+     * Return the geographical scope of this message (GSM/UMTS only).
+     *
+     * @return Geographical scope
+     */
+    public int getGeographicalScope() {
+        return mGeographicalScope;
+    }
+
+    /**
+     * Return the broadcast serial number of broadcast (message identifier for CDMA, or
+     * geographical scope + message code + update number for GSM/UMTS). The serial number plus
+     * the location code uniquely identify a cell broadcast for duplicate detection.
+     *
+     * @return the 16-bit CDMA message identifier or GSM/UMTS serial number
+     */
+    public int getSerialNumber() {
+        return mSerialNumber;
+    }
+
+    /**
+     * Return the location identifier for this message, consisting of the MCC/MNC as a
+     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+     * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the
+     * cell ID is also included. The {@link SmsCbLocation} object includes a method to test
+     * if the location is included within another location area or within a PLMN and CellLocation.
+     *
+     * @return the geographical location code for duplicate message detection
+     */
+    public SmsCbLocation getLocation() {
+        return mLocation;
+    }
+
+    /**
+     * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation
+     * of the category is radio technology specific. For ETWS and CMAS warnings, the information
+     * provided by the category is available via {@link #getEtwsWarningInfo()} or
+     * {@link #getCmasWarningInfo()} in a radio technology independent format.
+     *
+     * @return the radio technology specific service category
+     */
+    public int getServiceCategory() {
+        return mServiceCategory;
+    }
+
+    /**
+     * Get the ISO-639-1 language code for this message, or null if unspecified
+     *
+     * @return Language code
+     */
+    public String getLanguageCode() {
+        return mLanguage;
+    }
+
+    /**
+     * Get the body of this message, or null if no body available
+     *
+     * @return Body, or null
+     */
+    public String getMessageBody() {
+        return mBody;
+    }
+
+    /**
+     * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
+     * @return an integer representing 3GPP or 3GPP2 message format
+     */
+    public int getMessageFormat() {
+        return mMessageFormat;
+    }
+
+    /**
+     * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL}
+     * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return
+     * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
+     * @return an integer representing the message priority
+     */
+    public int getMessagePriority() {
+        return mPriority;
+    }
+
+    /**
+     * If this is an ETWS warning notification then this method will return an object containing
+     * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
+     * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
+     * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
+     * ETWS primary notification timestamp and digital signature if received.
+     *
+     * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
+     */
+    public SmsCbEtwsInfo getEtwsWarningInfo() {
+        return mEtwsWarningInfo;
+    }
+
+    /**
+     * If this is a CMAS warning notification then this method will return an object containing
+     * the CMAS message class, category, response type, severity, urgency and certainty.
+     * The message class is always present. Severity, urgency and certainty are present for CDMA
+     * warning notifications containing a type 1 elements record and for GSM and UMTS warnings
+     * except for the Presidential-level alert category. Category and response type are only
+     * available for CDMA notifications containing a type 1 elements record.
+     *
+     * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
+     */
+    public SmsCbCmasInfo getCmasWarningInfo() {
+        return mCmasWarningInfo;
+    }
+
+    /**
+     * Return whether this message is an emergency (PWS) message type.
+     * @return true if the message is a public warning notification; false otherwise
+     */
+    public boolean isEmergencyMessage() {
+        return mPriority == MESSAGE_PRIORITY_EMERGENCY;
+    }
+
+    /**
+     * Return whether this message is an ETWS warning alert.
+     * @return true if the message is an ETWS warning notification; false otherwise
+     */
+    public boolean isEtwsMessage() {
+        return mEtwsWarningInfo != null;
+    }
+
+    /**
+     * Return whether this message is a CMAS warning alert.
+     * @return true if the message is a CMAS warning notification; false otherwise
+     */
+    public boolean isCmasMessage() {
+        return mCmasWarningInfo != null;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
+                + mSerialNumber + ", location=" + mLocation + ", serviceCategory="
+                + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
+                + ", priority=" + mPriority
+                + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
+                + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsHeader.java b/telephony/java/com/android/internal/telephony/SmsHeader.java
new file mode 100644
index 0000000..b519b70
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsHeader.java
@@ -0,0 +1,314 @@
+/*
+ * 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 com.android.internal.telephony;
+
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.util.HexDump;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import java.util.ArrayList;
+
+/**
+ * SMS user data header, as specified in TS 23.040 9.2.3.24.
+ */
+public class SmsHeader {
+
+    // TODO(cleanup): this data structure is generally referred to as
+    // the 'user data header' or UDH, and so the class name should
+    // change to reflect this...
+
+    /** SMS user data header information element identifiers.
+     * (see TS 23.040 9.2.3.24)
+     */
+    public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE       = 0x00;
+    public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION     = 0x01;
+    public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT  = 0x04;
+    public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05;
+    public static final int ELT_ID_SMSC_CONTROL_PARAMS                = 0x06;
+    public static final int ELT_ID_UDH_SOURCE_INDICATION              = 0x07;
+    public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE      = 0x08;
+    public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL         = 0x09;
+    public static final int ELT_ID_TEXT_FORMATTING                    = 0x0A;
+    public static final int ELT_ID_PREDEFINED_SOUND                   = 0x0B;
+    public static final int ELT_ID_USER_DEFINED_SOUND                 = 0x0C;
+    public static final int ELT_ID_PREDEFINED_ANIMATION               = 0x0D;
+    public static final int ELT_ID_LARGE_ANIMATION                    = 0x0E;
+    public static final int ELT_ID_SMALL_ANIMATION                    = 0x0F;
+    public static final int ELT_ID_LARGE_PICTURE                      = 0x10;
+    public static final int ELT_ID_SMALL_PICTURE                      = 0x11;
+    public static final int ELT_ID_VARIABLE_PICTURE                   = 0x12;
+    public static final int ELT_ID_USER_PROMPT_INDICATOR              = 0x13;
+    public static final int ELT_ID_EXTENDED_OBJECT                    = 0x14;
+    public static final int ELT_ID_REUSED_EXTENDED_OBJECT             = 0x15;
+    public static final int ELT_ID_COMPRESSION_CONTROL                = 0x16;
+    public static final int ELT_ID_OBJECT_DISTR_INDICATOR             = 0x17;
+    public static final int ELT_ID_STANDARD_WVG_OBJECT                = 0x18;
+    public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT          = 0x19;
+    public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD   = 0x1A;
+    public static final int ELT_ID_RFC_822_EMAIL_HEADER               = 0x20;
+    public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT           = 0x21;
+    public static final int ELT_ID_REPLY_ADDRESS_ELEMENT              = 0x22;
+    public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION    = 0x23;
+    public static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT     = 0x24;
+    public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT    = 0x25;
+
+    public static final int PORT_WAP_PUSH = 2948;
+    public static final int PORT_WAP_WSP  = 9200;
+
+    public static class PortAddrs {
+        public int destPort;
+        public int origPort;
+        public boolean areEightBits;
+    }
+
+    public static class ConcatRef {
+        public int refNumber;
+        public int seqNumber;
+        public int msgCount;
+        public boolean isEightBits;
+    }
+
+    public static class SpecialSmsMsg {
+        public int msgIndType;
+        public int msgCount;
+    }
+
+    /**
+     * A header element that is not explicitly parsed, meaning not
+     * PortAddrs or ConcatRef or SpecialSmsMsg.
+     */
+    public static class MiscElt {
+        public int id;
+        public byte[] data;
+    }
+
+    public PortAddrs portAddrs;
+    public ConcatRef concatRef;
+    public ArrayList<SpecialSmsMsg> specialSmsMsgList = new ArrayList<SpecialSmsMsg>();
+    public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>();
+
+    /** 7 bit national language locking shift table, or 0 for GSM default 7 bit alphabet. */
+    public int languageTable;
+
+    /** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */
+    public int languageShiftTable;
+
+    public SmsHeader() {}
+
+    /**
+     * Create structured SmsHeader object from serialized byte array representation.
+     * (see TS 23.040 9.2.3.24)
+     * @param data is user data header bytes
+     * @return SmsHeader object
+     */
+    public static SmsHeader fromByteArray(byte[] data) {
+        ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+        SmsHeader smsHeader = new SmsHeader();
+        while (inStream.available() > 0) {
+            /**
+             * NOTE: as defined in the spec, ConcatRef and PortAddr
+             * fields should not reoccur, but if they do the last
+             * occurrence is to be used.  Also, for ConcatRef
+             * elements, if the count is zero, sequence is zero, or
+             * sequence is larger than count, the entire element is to
+             * be ignored.
+             */
+            int id = inStream.read();
+            int length = inStream.read();
+            ConcatRef concatRef;
+            PortAddrs portAddrs;
+            switch (id) {
+            case ELT_ID_CONCATENATED_8_BIT_REFERENCE:
+                concatRef = new ConcatRef();
+                concatRef.refNumber = inStream.read();
+                concatRef.msgCount = inStream.read();
+                concatRef.seqNumber = inStream.read();
+                concatRef.isEightBits = true;
+                if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
+                        concatRef.seqNumber <= concatRef.msgCount) {
+                    smsHeader.concatRef = concatRef;
+                }
+                break;
+            case ELT_ID_CONCATENATED_16_BIT_REFERENCE:
+                concatRef = new ConcatRef();
+                concatRef.refNumber = (inStream.read() << 8) | inStream.read();
+                concatRef.msgCount = inStream.read();
+                concatRef.seqNumber = inStream.read();
+                concatRef.isEightBits = false;
+                if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
+                        concatRef.seqNumber <= concatRef.msgCount) {
+                    smsHeader.concatRef = concatRef;
+                }
+                break;
+            case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT:
+                portAddrs = new PortAddrs();
+                portAddrs.destPort = inStream.read();
+                portAddrs.origPort = inStream.read();
+                portAddrs.areEightBits = true;
+                smsHeader.portAddrs = portAddrs;
+                break;
+            case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT:
+                portAddrs = new PortAddrs();
+                portAddrs.destPort = (inStream.read() << 8) | inStream.read();
+                portAddrs.origPort = (inStream.read() << 8) | inStream.read();
+                portAddrs.areEightBits = false;
+                smsHeader.portAddrs = portAddrs;
+                break;
+            case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
+                smsHeader.languageShiftTable = inStream.read();
+                break;
+            case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
+                smsHeader.languageTable = inStream.read();
+                break;
+            case ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION:
+                SpecialSmsMsg specialSmsMsg = new SpecialSmsMsg();
+                specialSmsMsg.msgIndType = inStream.read();
+                specialSmsMsg.msgCount = inStream.read();
+                smsHeader.specialSmsMsgList.add(specialSmsMsg);
+                break;
+            default:
+                MiscElt miscElt = new MiscElt();
+                miscElt.id = id;
+                miscElt.data = new byte[length];
+                inStream.read(miscElt.data, 0, length);
+                smsHeader.miscEltList.add(miscElt);
+            }
+        }
+        return smsHeader;
+    }
+
+    /**
+     * Create serialized byte array representation from structured SmsHeader object.
+     * (see TS 23.040 9.2.3.24)
+     * @return Byte array representing the SmsHeader
+     */
+    public static byte[] toByteArray(SmsHeader smsHeader) {
+        if ((smsHeader.portAddrs == null) &&
+            (smsHeader.concatRef == null) &&
+            (smsHeader.specialSmsMsgList.isEmpty()) &&
+            (smsHeader.miscEltList.isEmpty()) &&
+            (smsHeader.languageShiftTable == 0) &&
+            (smsHeader.languageTable == 0)) {
+            return null;
+        }
+
+        ByteArrayOutputStream outStream =
+                new ByteArrayOutputStream(SmsConstants.MAX_USER_DATA_BYTES);
+        ConcatRef concatRef = smsHeader.concatRef;
+        if (concatRef != null) {
+            if (concatRef.isEightBits) {
+                outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE);
+                outStream.write(3);
+                outStream.write(concatRef.refNumber);
+            } else {
+                outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE);
+                outStream.write(4);
+                outStream.write(concatRef.refNumber >>> 8);
+                outStream.write(concatRef.refNumber & 0x00FF);
+            }
+            outStream.write(concatRef.msgCount);
+            outStream.write(concatRef.seqNumber);
+        }
+        PortAddrs portAddrs = smsHeader.portAddrs;
+        if (portAddrs != null) {
+            if (portAddrs.areEightBits) {
+                outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT);
+                outStream.write(2);
+                outStream.write(portAddrs.destPort);
+                outStream.write(portAddrs.origPort);
+            } else {
+                outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT);
+                outStream.write(4);
+                outStream.write(portAddrs.destPort >>> 8);
+                outStream.write(portAddrs.destPort & 0x00FF);
+                outStream.write(portAddrs.origPort >>> 8);
+                outStream.write(portAddrs.origPort & 0x00FF);
+            }
+        }
+        if (smsHeader.languageShiftTable != 0) {
+            outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT);
+            outStream.write(1);
+            outStream.write(smsHeader.languageShiftTable);
+        }
+        if (smsHeader.languageTable != 0) {
+            outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT);
+            outStream.write(1);
+            outStream.write(smsHeader.languageTable);
+        }
+        for (SpecialSmsMsg specialSmsMsg : smsHeader.specialSmsMsgList) {
+            outStream.write(ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION);
+            outStream.write(2);
+            outStream.write(specialSmsMsg.msgIndType & 0xFF);
+            outStream.write(specialSmsMsg.msgCount & 0xFF);
+        }
+        for (MiscElt miscElt : smsHeader.miscEltList) {
+            outStream.write(miscElt.id);
+            outStream.write(miscElt.data.length);
+            outStream.write(miscElt.data, 0, miscElt.data.length);
+        }
+        return outStream.toByteArray();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("UserDataHeader ");
+        builder.append("{ ConcatRef ");
+        if (concatRef == null) {
+            builder.append("unset");
+        } else {
+            builder.append("{ refNumber=" + concatRef.refNumber);
+            builder.append(", msgCount=" + concatRef.msgCount);
+            builder.append(", seqNumber=" + concatRef.seqNumber);
+            builder.append(", isEightBits=" + concatRef.isEightBits);
+            builder.append(" }");
+        }
+        builder.append(", PortAddrs ");
+        if (portAddrs == null) {
+            builder.append("unset");
+        } else {
+            builder.append("{ destPort=" + portAddrs.destPort);
+            builder.append(", origPort=" + portAddrs.origPort);
+            builder.append(", areEightBits=" + portAddrs.areEightBits);
+            builder.append(" }");
+        }
+        if (languageShiftTable != 0) {
+            builder.append(", languageShiftTable=" + languageShiftTable);
+        }
+        if (languageTable != 0) {
+            builder.append(", languageTable=" + languageTable);
+        }
+        for (SpecialSmsMsg specialSmsMsg : specialSmsMsgList) {
+            builder.append(", SpecialSmsMsg ");
+            builder.append("{ msgIndType=" + specialSmsMsg.msgIndType);
+            builder.append(", msgCount=" + specialSmsMsg.msgCount);
+            builder.append(" }");
+        }
+        for (MiscElt miscElt : miscEltList) {
+            builder.append(", MiscElt ");
+            builder.append("{ id=" + miscElt.id);
+            builder.append(", length=" + miscElt.data.length);
+            builder.append(", data=" + HexDump.toHexString(miscElt.data));
+            builder.append(" }");
+        }
+        builder.append(" }");
+        return builder.toString();
+    }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
new file mode 100644
index 0000000..e5821dc
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -0,0 +1,433 @@
+/*
+ * 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.telephony;
+
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import java.text.BreakIterator;
+import java.util.Arrays;
+
+import android.provider.Telephony;
+import android.telephony.SmsMessage;
+import android.text.Emoji;
+
+/**
+ * Base class declaring the specific methods and members for SmsMessage.
+ * {@hide}
+ */
+public abstract class SmsMessageBase {
+    /** {@hide} The address of the SMSC. May be null */
+    protected String mScAddress;
+
+    /** {@hide} The address of the sender */
+    protected SmsAddress mOriginatingAddress;
+
+    /** {@hide} The message body as a string. May be null if the message isn't text */
+    protected String mMessageBody;
+
+    /** {@hide} */
+    protected String mPseudoSubject;
+
+    /** {@hide} Non-null if this is an email gateway message */
+    protected String mEmailFrom;
+
+    /** {@hide} Non-null if this is an email gateway message */
+    protected String mEmailBody;
+
+    /** {@hide} */
+    protected boolean mIsEmail;
+
+    /** {@hide} Time when SC (service centre) received the message */
+    protected long mScTimeMillis;
+
+    /** {@hide} The raw PDU of the message */
+    protected byte[] mPdu;
+
+    /** {@hide} The raw bytes for the user data section of the message */
+    protected byte[] mUserData;
+
+    /** {@hide} */
+    protected SmsHeader mUserDataHeader;
+
+    // "Message Waiting Indication Group"
+    // 23.038 Section 4
+    /** {@hide} */
+    protected boolean mIsMwi;
+
+    /** {@hide} */
+    protected boolean mMwiSense;
+
+    /** {@hide} */
+    protected boolean mMwiDontStore;
+
+    /**
+     * Indicates status for messages stored on the ICC.
+     */
+    protected int mStatusOnIcc = -1;
+
+    /**
+     * Record index of message in the EF.
+     */
+    protected int mIndexOnIcc = -1;
+
+    /** TP-Message-Reference - Message Reference of sent message. @hide */
+    public int mMessageRef;
+
+    // TODO(): This class is duplicated in SmsMessage.java. Refactor accordingly.
+    public static abstract class SubmitPduBase  {
+        public byte[] encodedScAddress; // Null if not applicable.
+        public byte[] encodedMessage;
+
+        @Override
+        public String toString() {
+            return "SubmitPdu: encodedScAddress = "
+                    + Arrays.toString(encodedScAddress)
+                    + ", encodedMessage = "
+                    + Arrays.toString(encodedMessage);
+        }
+    }
+
+    /**
+     * Returns the address of the SMS service center that relayed this message
+     * or null if there is none.
+     */
+    public String getServiceCenterAddress() {
+        return mScAddress;
+    }
+
+    /**
+     * Returns the originating address (sender) of this SMS message in String
+     * form or null if unavailable
+     */
+    public String getOriginatingAddress() {
+        if (mOriginatingAddress == null) {
+            return null;
+        }
+
+        return mOriginatingAddress.getAddressString();
+    }
+
+    /**
+     * Returns the originating address, or email from address if this message
+     * was from an email gateway. Returns null if originating address
+     * unavailable.
+     */
+    public String getDisplayOriginatingAddress() {
+        if (mIsEmail) {
+            return mEmailFrom;
+        } else {
+            return getOriginatingAddress();
+        }
+    }
+
+    /**
+     * Returns the message body as a String, if it exists and is text based.
+     * @return message body is there is one, otherwise null
+     */
+    public String getMessageBody() {
+        return mMessageBody;
+    }
+
+    /**
+     * Returns the class of this message.
+     */
+    public abstract SmsConstants.MessageClass getMessageClass();
+
+    /**
+     * Returns the message body, or email message body if this message was from
+     * an email gateway. Returns null if message body unavailable.
+     */
+    public String getDisplayMessageBody() {
+        if (mIsEmail) {
+            return mEmailBody;
+        } else {
+            return getMessageBody();
+        }
+    }
+
+    /**
+     * Unofficial convention of a subject line enclosed in parens empty string
+     * if not present
+     */
+    public String getPseudoSubject() {
+        return mPseudoSubject == null ? "" : mPseudoSubject;
+    }
+
+    /**
+     * Returns the service centre timestamp in currentTimeMillis() format
+     */
+    public long getTimestampMillis() {
+        return mScTimeMillis;
+    }
+
+    /**
+     * Returns true if message is an email.
+     *
+     * @return true if this message came through an email gateway and email
+     *         sender / subject / parsed body are available
+     */
+    public boolean isEmail() {
+        return mIsEmail;
+    }
+
+    /**
+     * @return if isEmail() is true, body of the email sent through the gateway.
+     *         null otherwise
+     */
+    public String getEmailBody() {
+        return mEmailBody;
+    }
+
+    /**
+     * @return if isEmail() is true, email from address of email sent through
+     *         the gateway. null otherwise
+     */
+    public String getEmailFrom() {
+        return mEmailFrom;
+    }
+
+    /**
+     * Get protocol identifier.
+     */
+    public abstract int getProtocolIdentifier();
+
+    /**
+     * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+     * SMS
+     */
+    public abstract boolean isReplace();
+
+    /**
+     * Returns true for CPHS MWI toggle message.
+     *
+     * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
+     *         B.4.2
+     */
+    public abstract boolean isCphsMwiMessage();
+
+    /**
+     * returns true if this message is a CPHS voicemail / message waiting
+     * indicator (MWI) clear message
+     */
+    public abstract boolean isMWIClearMessage();
+
+    /**
+     * returns true if this message is a CPHS voicemail / message waiting
+     * indicator (MWI) set message
+     */
+    public abstract boolean isMWISetMessage();
+
+    /**
+     * returns true if this message is a "Message Waiting Indication Group:
+     * Discard Message" notification and should not be stored.
+     */
+    public abstract boolean isMwiDontStore();
+
+    /**
+     * returns the user data section minus the user data header if one was
+     * present.
+     */
+    public byte[] getUserData() {
+        return mUserData;
+    }
+
+    /**
+     * Returns an object representing the user data header
+     *
+     * {@hide}
+     */
+    public SmsHeader getUserDataHeader() {
+        return mUserDataHeader;
+    }
+
+    /**
+     * TODO(cleanup): The term PDU is used in a seemingly non-unique
+     * manner -- for example, what is the difference between this byte
+     * array and the contents of SubmitPdu objects.  Maybe a more
+     * illustrative term would be appropriate.
+     */
+
+    /**
+     * Returns the raw PDU for the message.
+     */
+    public byte[] getPdu() {
+        return mPdu;
+    }
+
+    /**
+     * For an SMS-STATUS-REPORT message, this returns the status field from
+     * the status report.  This field indicates the status of a previously
+     * submitted SMS, if requested.  See TS 23.040, 9.2.3.15 TP-Status for a
+     * description of values.
+     *
+     * @return 0 indicates the previously sent message was received.
+     *         See TS 23.040, 9.9.2.3.15 for a description of other possible
+     *         values.
+     */
+    public abstract int getStatus();
+
+    /**
+     * Return true iff the message is a SMS-STATUS-REPORT message.
+     */
+    public abstract boolean isStatusReportMessage();
+
+    /**
+     * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+     * this message.
+     */
+    public abstract boolean isReplyPathPresent();
+
+    /**
+     * Returns the status of the message on the ICC (read, unread, sent, unsent).
+     *
+     * @return the status of the message on the ICC.  These are:
+     *         SmsManager.STATUS_ON_ICC_FREE
+     *         SmsManager.STATUS_ON_ICC_READ
+     *         SmsManager.STATUS_ON_ICC_UNREAD
+     *         SmsManager.STATUS_ON_ICC_SEND
+     *         SmsManager.STATUS_ON_ICC_UNSENT
+     */
+    public int getStatusOnIcc() {
+        return mStatusOnIcc;
+    }
+
+    /**
+     * Returns the record index of the message on the ICC (1-based index).
+     * @return the record index of the message on the ICC, or -1 if this
+     *         SmsMessage was not created from a ICC SMS EF record.
+     */
+    public int getIndexOnIcc() {
+        return mIndexOnIcc;
+    }
+
+    protected void parseMessageBody() {
+        // originatingAddress could be null if this message is from a status
+        // report.
+        if (mOriginatingAddress != null && mOriginatingAddress.couldBeEmailGateway()) {
+            extractEmailAddressFromMessageBody();
+        }
+    }
+
+    /**
+     * Try to parse this message as an email gateway message
+     * There are two ways specified in TS 23.040 Section 3.8 :
+     *  - SMS message "may have its TP-PID set for Internet electronic mail - MT
+     * SMS format: [<from-address><space>]<message> - "Depending on the
+     * nature of the gateway, the destination/origination address is either
+     * derived from the content of the SMS TP-OA or TP-DA field, or the
+     * TP-OA/TP-DA field contains a generic gateway address and the to/from
+     * address is added at the beginning as shown above." (which is supported here)
+     * - Multiple addresses separated by commas, no spaces, Subject field delimited
+     * by '()' or '##' and '#' Section 9.2.3.24.11 (which are NOT supported here)
+     */
+    protected void extractEmailAddressFromMessageBody() {
+
+        /* Some carriers may use " /" delimiter as below
+         *
+         * 1. [x@y][ ]/[subject][ ]/[body]
+         * -or-
+         * 2. [x@y][ ]/[body]
+         */
+         String[] parts = mMessageBody.split("( /)|( )", 2);
+         if (parts.length < 2) return;
+         mEmailFrom = parts[0];
+         mEmailBody = parts[1];
+         mIsEmail = Telephony.Mms.isEmailAddress(mEmailFrom);
+    }
+
+    /**
+     * Find the next position to start a new fragment of a multipart SMS.
+     *
+     * @param currentPosition current start position of the fragment
+     * @param byteLimit maximum number of bytes in the fragment
+     * @param msgBody text of the SMS in UTF-16 encoding
+     * @return the position to start the next fragment
+     */
+    public static int findNextUnicodePosition(
+            int currentPosition, int byteLimit, CharSequence msgBody) {
+        int nextPos = Math.min(currentPosition + byteLimit / 2, msgBody.length());
+        // Check whether the fragment ends in a character boundary. Some characters take 4-bytes
+        // in UTF-16 encoding. Many carriers cannot handle
+        // a fragment correctly if it does not end at a character boundary.
+        if (nextPos < msgBody.length()) {
+            BreakIterator breakIterator = BreakIterator.getCharacterInstance();
+            breakIterator.setText(msgBody.toString());
+            if (!breakIterator.isBoundary(nextPos)) {
+                int breakPos = breakIterator.preceding(nextPos);
+                while (breakPos + 4 <= nextPos
+                        && Emoji.isRegionalIndicatorSymbol(
+                            Character.codePointAt(msgBody, breakPos))
+                        && Emoji.isRegionalIndicatorSymbol(
+                            Character.codePointAt(msgBody, breakPos + 2))) {
+                    // skip forward over flags (pairs of Regional Indicator Symbol)
+                    breakPos += 4;
+                }
+                if (breakPos > currentPosition) {
+                    nextPos = breakPos;
+                } else if (Character.isHighSurrogate(msgBody.charAt(nextPos - 1))) {
+                    // no character boundary in this fragment, try to at least land on a code point
+                    nextPos -= 1;
+                }
+            }
+        }
+        return nextPos;
+    }
+
+    /**
+     * Calculate the TextEncodingDetails of a message encoded in Unicode.
+     */
+    public static TextEncodingDetails calcUnicodeEncodingDetails(CharSequence msgBody) {
+        TextEncodingDetails ted = new TextEncodingDetails();
+        int octets = msgBody.length() * 2;
+        ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
+        ted.codeUnitCount = msgBody.length();
+        if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
+            // If EMS is not supported, break down EMS into single segment SMS
+            // and add page info " x/y".
+            // In the case of UCS2 encoding type, we need 8 bytes for this
+            // but we only have 6 bytes from UDH, so truncate the limit for
+            // each segment by 2 bytes (1 char).
+            int maxUserDataBytesWithHeader = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+            if (!SmsMessage.hasEmsSupport()) {
+                // make sure total number of segments is less than 10
+                if (octets <= 9 * (maxUserDataBytesWithHeader - 2)) {
+                    maxUserDataBytesWithHeader -= 2;
+                }
+            }
+
+            int pos = 0;  // Index in code units.
+            int msgCount = 0;
+            while (pos < msgBody.length()) {
+                int nextPos = findNextUnicodePosition(pos, maxUserDataBytesWithHeader,
+                        msgBody);
+                if (nextPos == msgBody.length()) {
+                    ted.codeUnitsRemaining = pos + maxUserDataBytesWithHeader / 2 -
+                            msgBody.length();
+                }
+                pos = nextPos;
+                msgCount++;
+            }
+            ted.msgCount = msgCount;
+        } else {
+            ted.msgCount = 1;
+            ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets) / 2;
+        }
+
+        return ted;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index ce4e7e2..55a8b0b 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -402,7 +402,7 @@
      * <ul>
      *   <li>apnType</li><dd>A string with the apn type.</dd>
      *   <li>redirectionUrl</li><dd>redirection url string</dd>
-     *   <li>subId</dt><li>Sub Id which associated the data connection failure.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
      * </ul>
      * <p class="note">This is a protected intent that can only be sent by the system.</p>
      */
@@ -415,7 +415,7 @@
      * <ul>
      *   <li>apnType</li><dd>A string with the apn type.</dd>
      *   <li>errorCode</li><dd>A integer with dataFailCause.</dd>
-     *   <li>subId</dt><li>Sub Id which associated the data connection failure.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
      * </ul>
      * <p class="note">This is a protected intent that can only be sent by the system. </p>
      */
@@ -432,13 +432,25 @@
      *                        IPV4V6)</dd>
      *   <li>pcoId</li><dd>An integer indicating the pco id for the data.</dd>
      *   <li>pcoValue</li><dd>A byte array of pco data read from modem.</dd>
-     *   <li>subId</dt><li>Sub Id which associated the data connection.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the data connection.</dd>
      * </ul>
      * <p class="note">This is a protected intent that can only be sent by the system. </p>
      */
     public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
             "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
 
+    /**
+     * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
+     * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
+     */
+    public static final String ACTION_CARRIER_SIGNAL_RESET =
+            "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
+
     // CARRIER_SIGNAL_ACTION extra keys
     public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl";
     public static final String EXTRA_ERROR_CODE_KEY = "errorCode";
diff --git a/telephony/java/com/android/internal/telephony/cdma/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/BearerData.java
new file mode 100644
index 0000000..1de72db
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/BearerData.java
@@ -0,0 +1,2000 @@
+/*
+ * 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.telephony.cdma.sms;
+
+import android.content.res.Resources;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.telephony.cdma.CdmaSmsCbProgramResults;
+import android.text.format.Time;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.BitwiseOutputStream;
+
+import java.util.ArrayList;
+import java.util.TimeZone;
+
+/**
+ * An object to encode and decode CDMA SMS bearer data.
+ */
+public final class BearerData {
+    private final static String LOG_TAG = "BearerData";
+
+    /**
+     * Bearer Data Subparameter Identifiers
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
+     * NOTE: Commented subparameter types are not implemented.
+     */
+    private final static byte SUBPARAM_MESSAGE_IDENTIFIER               = 0x00;
+    private final static byte SUBPARAM_USER_DATA                        = 0x01;
+    private final static byte SUBPARAM_USER_RESPONSE_CODE               = 0x02;
+    private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP        = 0x03;
+    private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE         = 0x04;
+    private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE         = 0x05;
+    private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE  = 0x06;
+    private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE  = 0x07;
+    private final static byte SUBPARAM_PRIORITY_INDICATOR               = 0x08;
+    private final static byte SUBPARAM_PRIVACY_INDICATOR                = 0x09;
+    private final static byte SUBPARAM_REPLY_OPTION                     = 0x0A;
+    private final static byte SUBPARAM_NUMBER_OF_MESSAGES               = 0x0B;
+    private final static byte SUBPARAM_ALERT_ON_MESSAGE_DELIVERY        = 0x0C;
+    private final static byte SUBPARAM_LANGUAGE_INDICATOR               = 0x0D;
+    private final static byte SUBPARAM_CALLBACK_NUMBER                  = 0x0E;
+    private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE             = 0x0F;
+    //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA      = 0x10;
+    private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX            = 0x11;
+    private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA    = 0x12;
+    private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13;
+    private final static byte SUBPARAM_MESSAGE_STATUS                   = 0x14;
+    //private final static byte SUBPARAM_TP_FAILURE_CAUSE                 = 0x15;
+    //private final static byte SUBPARAM_ENHANCED_VMN                     = 0x16;
+    //private final static byte SUBPARAM_ENHANCED_VMN_ACK                 = 0x17;
+
+    // All other values after this are reserved.
+    private final static byte SUBPARAM_ID_LAST_DEFINED                    = 0x17;
+
+    /**
+     * Supported message types for CDMA SMS messages
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
+     */
+    public static final int MESSAGE_TYPE_DELIVER        = 0x01;
+    public static final int MESSAGE_TYPE_SUBMIT         = 0x02;
+    public static final int MESSAGE_TYPE_CANCELLATION   = 0x03;
+    public static final int MESSAGE_TYPE_DELIVERY_ACK   = 0x04;
+    public static final int MESSAGE_TYPE_USER_ACK       = 0x05;
+    public static final int MESSAGE_TYPE_READ_ACK       = 0x06;
+    public static final int MESSAGE_TYPE_DELIVER_REPORT = 0x07;
+    public static final int MESSAGE_TYPE_SUBMIT_REPORT  = 0x08;
+
+    public int messageType;
+
+    /**
+     * 16-bit value indicating the message ID, which increments modulo 65536.
+     * (Special rules apply for WAP-messages.)
+     * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+     */
+    public int messageId;
+
+    /**
+     * Supported priority modes for CDMA SMS messages
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+     */
+    public static final int PRIORITY_NORMAL        = 0x0;
+    public static final int PRIORITY_INTERACTIVE   = 0x1;
+    public static final int PRIORITY_URGENT        = 0x2;
+    public static final int PRIORITY_EMERGENCY     = 0x3;
+
+    public boolean priorityIndicatorSet = false;
+    public int priority = PRIORITY_NORMAL;
+
+    /**
+     * Supported privacy modes for CDMA SMS messages
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.10-1)
+     */
+    public static final int PRIVACY_NOT_RESTRICTED = 0x0;
+    public static final int PRIVACY_RESTRICTED     = 0x1;
+    public static final int PRIVACY_CONFIDENTIAL   = 0x2;
+    public static final int PRIVACY_SECRET         = 0x3;
+
+    public boolean privacyIndicatorSet = false;
+    public int privacy = PRIVACY_NOT_RESTRICTED;
+
+    /**
+     * Supported alert priority modes for CDMA SMS messages
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.13-1)
+     */
+    public static final int ALERT_DEFAULT          = 0x0;
+    public static final int ALERT_LOW_PRIO         = 0x1;
+    public static final int ALERT_MEDIUM_PRIO      = 0x2;
+    public static final int ALERT_HIGH_PRIO        = 0x3;
+
+    public boolean alertIndicatorSet = false;
+    public int alert = ALERT_DEFAULT;
+
+    /**
+     * Supported display modes for CDMA SMS messages.  Display mode is
+     * a 2-bit value used to indicate to the mobile station when to
+     * display the received message.  (See 3GPP2 C.S0015-B, v2,
+     * 4.5.16)
+     */
+    public static final int DISPLAY_MODE_IMMEDIATE      = 0x0;
+    public static final int DISPLAY_MODE_DEFAULT        = 0x1;
+    public static final int DISPLAY_MODE_USER           = 0x2;
+
+    public boolean displayModeSet = false;
+    public int displayMode = DISPLAY_MODE_DEFAULT;
+
+    /**
+     * Language Indicator values.  NOTE: the spec (3GPP2 C.S0015-B,
+     * v2, 4.5.14) is ambiguous as to the meaning of this field, as it
+     * refers to C.R1001-D but that reference has been crossed out.
+     * It would seem reasonable to assume the values from C.R1001-F
+     * (table 9.2-1) are to be used instead.
+     */
+    public static final int LANGUAGE_UNKNOWN  = 0x00;
+    public static final int LANGUAGE_ENGLISH  = 0x01;
+    public static final int LANGUAGE_FRENCH   = 0x02;
+    public static final int LANGUAGE_SPANISH  = 0x03;
+    public static final int LANGUAGE_JAPANESE = 0x04;
+    public static final int LANGUAGE_KOREAN   = 0x05;
+    public static final int LANGUAGE_CHINESE  = 0x06;
+    public static final int LANGUAGE_HEBREW   = 0x07;
+
+    public boolean languageIndicatorSet = false;
+    public int language = LANGUAGE_UNKNOWN;
+
+    /**
+     * SMS Message Status Codes.  The first component of the Message
+     * status indicates if an error has occurred and whether the error
+     * is considered permanent or temporary.  The second component of
+     * the Message status indicates the cause of the error (if any).
+     * (See 3GPP2 C.S0015-B, v2.0, 4.5.21)
+     */
+    /* no-error codes */
+    public static final int ERROR_NONE                   = 0x00;
+    public static final int STATUS_ACCEPTED              = 0x00;
+    public static final int STATUS_DEPOSITED_TO_INTERNET = 0x01;
+    public static final int STATUS_DELIVERED             = 0x02;
+    public static final int STATUS_CANCELLED             = 0x03;
+    /* temporary-error and permanent-error codes */
+    public static final int ERROR_TEMPORARY              = 0x02;
+    public static final int STATUS_NETWORK_CONGESTION    = 0x04;
+    public static final int STATUS_NETWORK_ERROR         = 0x05;
+    public static final int STATUS_UNKNOWN_ERROR         = 0x1F;
+    /* permanent-error codes */
+    public static final int ERROR_PERMANENT              = 0x03;
+    public static final int STATUS_CANCEL_FAILED         = 0x06;
+    public static final int STATUS_BLOCKED_DESTINATION   = 0x07;
+    public static final int STATUS_TEXT_TOO_LONG         = 0x08;
+    public static final int STATUS_DUPLICATE_MESSAGE     = 0x09;
+    public static final int STATUS_INVALID_DESTINATION   = 0x0A;
+    public static final int STATUS_MESSAGE_EXPIRED       = 0x0D;
+    /* undefined-status codes */
+    public static final int ERROR_UNDEFINED              = 0xFF;
+    public static final int STATUS_UNDEFINED             = 0xFF;
+
+    public boolean messageStatusSet = false;
+    public int errorClass = ERROR_UNDEFINED;
+    public int messageStatus = STATUS_UNDEFINED;
+
+    /**
+     * 1-bit value that indicates whether a User Data Header (UDH) is present.
+     * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+     *
+     * NOTE: during encoding, this value will be set based on the
+     * presence of a UDH in the structured data, any existing setting
+     * will be overwritten.
+     */
+    public boolean hasUserDataHeader;
+
+    /**
+     * provides the information for the user data
+     * (e.g. padding bits, user data, user data header, etc)
+     * (See 3GPP2 C.S.0015-B, v2, 4.5.2)
+     */
+    public UserData userData;
+
+    /**
+     * The User Response Code subparameter is used in the SMS User
+     * Acknowledgment Message to respond to previously received short
+     * messages. This message center-specific element carries the
+     * identifier of a predefined response. (See 3GPP2 C.S.0015-B, v2,
+     * 4.5.3)
+     */
+    public boolean userResponseCodeSet = false;
+    public int userResponseCode;
+
+    /**
+     * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4
+     */
+    public static class TimeStamp extends Time {
+
+        public TimeStamp() {
+            super(TimeZone.getDefault().getID());   // 3GPP2 timestamps use the local timezone
+        }
+
+        public static TimeStamp fromByteArray(byte[] data) {
+            TimeStamp ts = new TimeStamp();
+            // C.S0015-B v2.0, 4.5.4: range is 1996-2095
+            int year = IccUtils.cdmaBcdByteToInt(data[0]);
+            if (year > 99 || year < 0) return null;
+            ts.year = year >= 96 ? year + 1900 : year + 2000;
+            int month = IccUtils.cdmaBcdByteToInt(data[1]);
+            if (month < 1 || month > 12) return null;
+            ts.month = month - 1;
+            int day = IccUtils.cdmaBcdByteToInt(data[2]);
+            if (day < 1 || day > 31) return null;
+            ts.monthDay = day;
+            int hour = IccUtils.cdmaBcdByteToInt(data[3]);
+            if (hour < 0 || hour > 23) return null;
+            ts.hour = hour;
+            int minute = IccUtils.cdmaBcdByteToInt(data[4]);
+            if (minute < 0 || minute > 59) return null;
+            ts.minute = minute;
+            int second = IccUtils.cdmaBcdByteToInt(data[5]);
+            if (second < 0 || second > 59) return null;
+            ts.second = second;
+            return ts;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("TimeStamp ");
+            builder.append("{ year=" + year);
+            builder.append(", month=" + month);
+            builder.append(", day=" + monthDay);
+            builder.append(", hour=" + hour);
+            builder.append(", minute=" + minute);
+            builder.append(", second=" + second);
+            builder.append(" }");
+            return builder.toString();
+        }
+    }
+
+    public TimeStamp msgCenterTimeStamp;
+    public TimeStamp validityPeriodAbsolute;
+    public TimeStamp deferredDeliveryTimeAbsolute;
+
+    /**
+     * Relative time is specified as one byte, the value of which
+     * falls into a series of ranges, as specified below.  The idea is
+     * that shorter time intervals allow greater precision -- the
+     * value means minutes from zero until the MINS_LIMIT (inclusive),
+     * upon which it means hours until the HOURS_LIMIT, and so
+     * forth. (See 3GPP2 C.S0015-B, v2, 4.5.6-1)
+     */
+    public static final int RELATIVE_TIME_MINS_LIMIT      = 143;
+    public static final int RELATIVE_TIME_HOURS_LIMIT     = 167;
+    public static final int RELATIVE_TIME_DAYS_LIMIT      = 196;
+    public static final int RELATIVE_TIME_WEEKS_LIMIT     = 244;
+    public static final int RELATIVE_TIME_INDEFINITE      = 245;
+    public static final int RELATIVE_TIME_NOW             = 246;
+    public static final int RELATIVE_TIME_MOBILE_INACTIVE = 247;
+    public static final int RELATIVE_TIME_RESERVED        = 248;
+
+    public boolean validityPeriodRelativeSet;
+    public int validityPeriodRelative;
+    public boolean deferredDeliveryTimeRelativeSet;
+    public int deferredDeliveryTimeRelative;
+
+    /**
+     * The Reply Option subparameter contains 1-bit values which
+     * indicate whether SMS acknowledgment is requested or not.  (See
+     * 3GPP2 C.S0015-B, v2, 4.5.11)
+     */
+    public boolean userAckReq;
+    public boolean deliveryAckReq;
+    public boolean readAckReq;
+    public boolean reportReq;
+
+    /**
+     * The Number of Messages subparameter (8-bit value) is a decimal
+     * number in the 0 to 99 range representing the number of messages
+     * stored at the Voice Mail System. This element is used by the
+     * Voice Mail Notification service.  (See 3GPP2 C.S0015-B, v2,
+     * 4.5.12)
+     */
+    public int numberOfMessages;
+
+    /**
+     * The Message Deposit Index subparameter is assigned by the
+     * message center as a unique index to the contents of the User
+     * Data subparameter in each message sent to a particular mobile
+     * station. The mobile station, when replying to a previously
+     * received short message which included a Message Deposit Index
+     * subparameter, may include the Message Deposit Index of the
+     * received message to indicate to the message center that the
+     * original contents of the message are to be included in the
+     * reply.  (See 3GPP2 C.S0015-B, v2, 4.5.18)
+     */
+    public int depositIndex;
+
+    /**
+     * 4-bit or 8-bit value that indicates the number to be dialed in reply to a
+     * received SMS message.
+     * (See 3GPP2 C.S0015-B, v2, 4.5.15)
+     */
+    public CdmaSmsAddress callbackNumber;
+
+    /**
+     * CMAS warning notification information.
+     * @see #decodeCmasUserData(BearerData, int)
+     */
+    public SmsCbCmasInfo cmasWarningInfo;
+
+    /**
+     * The Service Category Program Data subparameter is used to enable and disable
+     * SMS broadcast service categories to display. If this subparameter is present,
+     * this field will contain a list of one or more
+     * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the
+     * operation(s) to perform.
+     */
+    public ArrayList<CdmaSmsCbProgramData> serviceCategoryProgramData;
+
+    /**
+     * The Service Category Program Results subparameter informs the message center
+     * of the results of a Service Category Program Data request.
+     */
+    public ArrayList<CdmaSmsCbProgramResults> serviceCategoryProgramResults;
+
+
+    private static class CodingException extends Exception {
+        public CodingException(String s) {
+            super(s);
+        }
+    }
+
+    /**
+     * Returns the language indicator as a two-character ISO 639 string.
+     * @return a two character ISO 639 language code
+     */
+    public String getLanguage() {
+        return getLanguageCodeForValue(language);
+    }
+
+    /**
+     * Converts a CDMA language indicator value to an ISO 639 two character language code.
+     * @param languageValue the CDMA language value to convert
+     * @return the two character ISO 639 language code for the specified value, or null if unknown
+     */
+    private static String getLanguageCodeForValue(int languageValue) {
+        switch (languageValue) {
+            case LANGUAGE_ENGLISH:
+                return "en";
+
+            case LANGUAGE_FRENCH:
+                return "fr";
+
+            case LANGUAGE_SPANISH:
+                return "es";
+
+            case LANGUAGE_JAPANESE:
+                return "ja";
+
+            case LANGUAGE_KOREAN:
+                return "ko";
+
+            case LANGUAGE_CHINESE:
+                return "zh";
+
+            case LANGUAGE_HEBREW:
+                return "he";
+
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("BearerData ");
+        builder.append("{ messageType=" + messageType);
+        builder.append(", messageId=" + messageId);
+        builder.append(", priority=" + (priorityIndicatorSet ? priority : "unset"));
+        builder.append(", privacy=" + (privacyIndicatorSet ? privacy : "unset"));
+        builder.append(", alert=" + (alertIndicatorSet ? alert : "unset"));
+        builder.append(", displayMode=" + (displayModeSet ? displayMode : "unset"));
+        builder.append(", language=" + (languageIndicatorSet ? language : "unset"));
+        builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset"));
+        builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset"));
+        builder.append(", msgCenterTimeStamp=" +
+                ((msgCenterTimeStamp != null) ? msgCenterTimeStamp : "unset"));
+        builder.append(", validityPeriodAbsolute=" +
+                ((validityPeriodAbsolute != null) ? validityPeriodAbsolute : "unset"));
+        builder.append(", validityPeriodRelative=" +
+                ((validityPeriodRelativeSet) ? validityPeriodRelative : "unset"));
+        builder.append(", deferredDeliveryTimeAbsolute=" +
+                ((deferredDeliveryTimeAbsolute != null) ? deferredDeliveryTimeAbsolute : "unset"));
+        builder.append(", deferredDeliveryTimeRelative=" +
+                ((deferredDeliveryTimeRelativeSet) ? deferredDeliveryTimeRelative : "unset"));
+        builder.append(", userAckReq=" + userAckReq);
+        builder.append(", deliveryAckReq=" + deliveryAckReq);
+        builder.append(", readAckReq=" + readAckReq);
+        builder.append(", reportReq=" + reportReq);
+        builder.append(", numberOfMessages=" + numberOfMessages);
+        builder.append(", callbackNumber=" + Rlog.pii(LOG_TAG, callbackNumber));
+        builder.append(", depositIndex=" + depositIndex);
+        builder.append(", hasUserDataHeader=" + hasUserDataHeader);
+        builder.append(", userData=" + userData);
+        builder.append(" }");
+        return builder.toString();
+    }
+
+    private static void encodeMessageId(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 3);
+        outStream.write(4, bData.messageType);
+        outStream.write(8, bData.messageId >> 8);
+        outStream.write(8, bData.messageId);
+        outStream.write(1, bData.hasUserDataHeader ? 1 : 0);
+        outStream.skip(3);
+    }
+
+    private static int countAsciiSeptets(CharSequence msg, boolean force) {
+        int msgLen = msg.length();
+        if (force) return msgLen;
+        for (int i = 0; i < msgLen; i++) {
+            if (UserData.charToAscii.get(msg.charAt(i), -1) == -1) {
+                return -1;
+            }
+        }
+        return msgLen;
+    }
+
+    /**
+     * Calculate the message text encoding length, fragmentation, and other details.
+     *
+     * @param msg message text
+     * @param force7BitEncoding ignore (but still count) illegal characters if true
+     * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
+     * @return septet count, or -1 on failure
+     */
+    public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg,
+            boolean force7BitEncoding, boolean isEntireMsg) {
+        TextEncodingDetails ted;
+        int septets = countAsciiSeptets(msg, force7BitEncoding);
+        if (septets != -1 && septets <= SmsConstants.MAX_USER_DATA_SEPTETS) {
+            ted = new TextEncodingDetails();
+            ted.msgCount = 1;
+            ted.codeUnitCount = septets;
+            ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets;
+            ted.codeUnitSize = SmsConstants.ENCODING_7BIT;
+        } else {
+            ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength(
+                    msg, force7BitEncoding);
+            if (ted.msgCount == 1 && ted.codeUnitSize == SmsConstants.ENCODING_7BIT &&
+                    isEntireMsg) {
+                // We don't support single-segment EMS, so calculate for 16-bit
+                // TODO: Consider supporting single-segment EMS
+                return SmsMessageBase.calcUnicodeEncodingDetails(msg);
+            }
+        }
+        return ted;
+    }
+
+    private static byte[] encode7bitAscii(String msg, boolean force)
+        throws CodingException
+    {
+        try {
+            BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length());
+            int msgLen = msg.length();
+            for (int i = 0; i < msgLen; i++) {
+                int charCode = UserData.charToAscii.get(msg.charAt(i), -1);
+                if (charCode == -1) {
+                    if (force) {
+                        outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR);
+                    } else {
+                        throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")");
+                    }
+                } else {
+                    outStream.write(7, charCode);
+                }
+            }
+            return outStream.toByteArray();
+        } catch (BitwiseOutputStream.AccessException ex) {
+            throw new CodingException("7bit ASCII encode failed: " + ex);
+        }
+    }
+
+    private static byte[] encodeUtf16(String msg)
+        throws CodingException
+    {
+        try {
+            return msg.getBytes("utf-16be");
+        } catch (java.io.UnsupportedEncodingException ex) {
+            throw new CodingException("UTF-16 encode failed: " + ex);
+        }
+    }
+
+    private static class Gsm7bitCodingResult {
+        int septets;
+        byte[] data;
+    }
+
+    private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force)
+        throws CodingException
+    {
+        try {
+            /*
+             * TODO(cleanup): It would be nice if GsmAlphabet provided
+             * an option to produce just the data without prepending
+             * the septet count, as this function is really just a
+             * wrapper to strip that off.  Not to mention that the
+             * septet count is generally known prior to invocation of
+             * the encoder.  Note that it cannot be derived from the
+             * resulting array length, since that cannot distinguish
+             * if the last contains either 1 or 8 valid bits.
+             *
+             * TODO(cleanup): The BitwiseXStreams could also be
+             * extended with byte-wise reversed endianness read/write
+             * routines to allow a corresponding implementation of
+             * stringToGsm7BitPacked, and potentially directly support
+             * access to the main bitwise stream from encode/decode.
+             */
+            byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0);
+            Gsm7bitCodingResult result = new Gsm7bitCodingResult();
+            result.data = new byte[fullData.length - 1];
+            System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1);
+            result.septets = fullData[0] & 0x00FF;
+            return result;
+        } catch (com.android.internal.telephony.EncodeException ex) {
+            throw new CodingException("7bit GSM encode failed: " + ex);
+        }
+    }
+
+    private static void encode7bitEms(UserData uData, byte[] udhData, boolean force)
+        throws CodingException
+    {
+        int udhBytes = udhData.length + 1;  // Add length octet.
+        int udhSeptets = ((udhBytes * 8) + 6) / 7;
+        Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force);
+        uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+        uData.msgEncodingSet = true;
+        uData.numFields = gcr.septets;
+        uData.payload = gcr.data;
+        uData.payload[0] = (byte)udhData.length;
+        System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
+    }
+
+    private static void encode16bitEms(UserData uData, byte[] udhData)
+        throws CodingException
+    {
+        byte[] payload = encodeUtf16(uData.payloadStr);
+        int udhBytes = udhData.length + 1;  // Add length octet.
+        int udhCodeUnits = (udhBytes + 1) / 2;
+        int payloadCodeUnits = payload.length / 2;
+        uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+        uData.msgEncodingSet = true;
+        uData.numFields = udhCodeUnits + payloadCodeUnits;
+        uData.payload = new byte[uData.numFields * 2];
+        uData.payload[0] = (byte)udhData.length;
+        System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
+        System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length);
+    }
+
+    private static void encodeEmsUserDataPayload(UserData uData)
+        throws CodingException
+    {
+        byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader);
+        if (uData.msgEncodingSet) {
+            if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
+                encode7bitEms(uData, headerData, true);
+            } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
+                encode16bitEms(uData, headerData);
+            } else {
+                throw new CodingException("unsupported EMS user data encoding (" +
+                                          uData.msgEncoding + ")");
+            }
+        } else {
+            try {
+                encode7bitEms(uData, headerData, false);
+            } catch (CodingException ex) {
+                encode16bitEms(uData, headerData);
+            }
+        }
+    }
+
+    private static byte[] encodeShiftJis(String msg) throws CodingException {
+        try {
+            return msg.getBytes("Shift_JIS");
+        } catch (java.io.UnsupportedEncodingException ex) {
+            throw new CodingException("Shift-JIS encode failed: " + ex);
+        }
+    }
+
+    private static void encodeUserDataPayload(UserData uData)
+        throws CodingException
+    {
+        if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) {
+            Rlog.e(LOG_TAG, "user data with null payloadStr");
+            uData.payloadStr = "";
+        }
+
+        if (uData.userDataHeader != null) {
+            encodeEmsUserDataPayload(uData);
+            return;
+        }
+
+        if (uData.msgEncodingSet) {
+            if (uData.msgEncoding == UserData.ENCODING_OCTET) {
+                if (uData.payload == null) {
+                    Rlog.e(LOG_TAG, "user data with octet encoding but null payload");
+                    uData.payload = new byte[0];
+                    uData.numFields = 0;
+                } else {
+                    uData.numFields = uData.payload.length;
+                }
+            } else {
+                if (uData.payloadStr == null) {
+                    Rlog.e(LOG_TAG, "non-octet user data with null payloadStr");
+                    uData.payloadStr = "";
+                }
+                if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
+                    Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true);
+                    uData.payload = gcr.data;
+                    uData.numFields = gcr.septets;
+                } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
+                    uData.payload = encode7bitAscii(uData.payloadStr, true);
+                    uData.numFields = uData.payloadStr.length();
+                } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
+                    uData.payload = encodeUtf16(uData.payloadStr);
+                    uData.numFields = uData.payloadStr.length();
+                } else if (uData.msgEncoding == UserData.ENCODING_SHIFT_JIS) {
+                    uData.payload = encodeShiftJis(uData.payloadStr);
+                    uData.numFields = uData.payload.length;
+                } else {
+                    throw new CodingException("unsupported user data encoding (" +
+                                              uData.msgEncoding + ")");
+                }
+            }
+        } else {
+            try {
+                uData.payload = encode7bitAscii(uData.payloadStr, false);
+                uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
+            } catch (CodingException ex) {
+                uData.payload = encodeUtf16(uData.payloadStr);
+                uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+            }
+            uData.numFields = uData.payloadStr.length();
+            uData.msgEncodingSet = true;
+        }
+    }
+
+    private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException, CodingException
+    {
+        /*
+         * TODO(cleanup): Do we really need to set userData.payload as
+         * a side effect of encoding?  If not, we could avoid data
+         * copies by passing outStream directly.
+         */
+        encodeUserDataPayload(bData.userData);
+        bData.hasUserDataHeader = bData.userData.userDataHeader != null;
+
+        if (bData.userData.payload.length > SmsConstants.MAX_USER_DATA_BYTES) {
+            throw new CodingException("encoded user data too large (" +
+                                      bData.userData.payload.length +
+                                      " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)");
+        }
+
+        /*
+         * TODO(cleanup): figure out what the right answer is WRT paddingBits field
+         *
+         *   userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7);
+         *   userData.paddingBits = 0; // XXX this seems better, but why?
+         *
+         */
+        int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits;
+        int paramBits = dataBits + 13;
+        if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+            (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+            paramBits += 8;
+        }
+        int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
+        int paddingBits = (paramBytes * 8) - paramBits;
+        outStream.write(8, paramBytes);
+        outStream.write(5, bData.userData.msgEncoding);
+        if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+            (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+            outStream.write(8, bData.userData.msgType);
+        }
+        outStream.write(8, bData.userData.numFields);
+        outStream.writeByteArray(dataBits, bData.userData.payload);
+        if (paddingBits > 0) outStream.write(paddingBits, 0);
+    }
+
+    private static void encodeReplyOption(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 1);
+        outStream.write(1, bData.userAckReq     ? 1 : 0);
+        outStream.write(1, bData.deliveryAckReq ? 1 : 0);
+        outStream.write(1, bData.readAckReq     ? 1 : 0);
+        outStream.write(1, bData.reportReq      ? 1 : 0);
+        outStream.write(4, 0);
+    }
+
+    private static byte[] encodeDtmfSmsAddress(String address) {
+        int digits = address.length();
+        int dataBits = digits * 4;
+        int dataBytes = (dataBits / 8);
+        dataBytes += (dataBits % 8) > 0 ? 1 : 0;
+        byte[] rawData = new byte[dataBytes];
+        for (int i = 0; i < digits; i++) {
+            char c = address.charAt(i);
+            int val = 0;
+            if ((c >= '1') && (c <= '9')) val = c - '0';
+            else if (c == '0') val = 10;
+            else if (c == '*') val = 11;
+            else if (c == '#') val = 12;
+            else return null;
+            rawData[i / 2] |= val << (4 - ((i % 2) * 4));
+        }
+        return rawData;
+    }
+
+    /*
+     * TODO(cleanup): CdmaSmsAddress encoding should make use of
+     * CdmaSmsAddress.parse provided that DTMF encoding is unified,
+     * and the difference in 4-bit vs. 8-bit is resolved.
+     */
+
+    private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException {
+        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+            try {
+                addr.origBytes = addr.address.getBytes("US-ASCII");
+            } catch (java.io.UnsupportedEncodingException ex) {
+                throw new CodingException("invalid SMS address, cannot convert to ASCII");
+            }
+        } else {
+            addr.origBytes = encodeDtmfSmsAddress(addr.address);
+        }
+    }
+
+    private static void encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException, CodingException
+    {
+        CdmaSmsAddress addr = bData.callbackNumber;
+        encodeCdmaSmsAddress(addr);
+        int paramBits = 9;
+        int dataBits = 0;
+        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+            paramBits += 7;
+            dataBits = addr.numberOfDigits * 8;
+        } else {
+            dataBits = addr.numberOfDigits * 4;
+        }
+        paramBits += dataBits;
+        int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
+        int paddingBits = (paramBytes * 8) - paramBits;
+        outStream.write(8, paramBytes);
+        outStream.write(1, addr.digitMode);
+        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+            outStream.write(3, addr.ton);
+            outStream.write(4, addr.numberPlan);
+        }
+        outStream.write(8, addr.numberOfDigits);
+        outStream.writeByteArray(dataBits, addr.origBytes);
+        if (paddingBits > 0) outStream.write(paddingBits, 0);
+    }
+
+    private static void encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 1);
+        outStream.write(2, bData.errorClass);
+        outStream.write(6, bData.messageStatus);
+    }
+
+    private static void encodeMsgCount(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 1);
+        outStream.write(8, bData.numberOfMessages);
+    }
+
+    private static void encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 1);
+        outStream.write(8, bData.validityPeriodRelative);
+    }
+
+    private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 1);
+        outStream.write(2, bData.privacy);
+        outStream.skip(6);
+    }
+
+    private static void encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 1);
+        outStream.write(8, bData.language);
+    }
+
+    private static void encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 1);
+        outStream.write(2, bData.displayMode);
+        outStream.skip(6);
+    }
+
+    private static void encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 1);
+        outStream.write(2, bData.priority);
+        outStream.skip(6);
+    }
+
+    private static void encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        outStream.write(8, 1);
+        outStream.write(2, bData.alert);
+        outStream.skip(6);
+    }
+
+    private static void encodeScpResults(BearerData bData, BitwiseOutputStream outStream)
+        throws BitwiseOutputStream.AccessException
+    {
+        ArrayList<CdmaSmsCbProgramResults> results = bData.serviceCategoryProgramResults;
+        outStream.write(8, (results.size() * 4));   // 4 octets per program result
+        for (CdmaSmsCbProgramResults result : results) {
+            int category = result.getCategory();
+            outStream.write(8, category >> 8);
+            outStream.write(8, category);
+            outStream.write(8, result.getLanguage());
+            outStream.write(4, result.getCategoryResult());
+            outStream.skip(4);
+        }
+    }
+
+    /**
+     * Create serialized representation for BearerData object.
+     * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+     *
+     * @param bData an instance of BearerData.
+     *
+     * @return byte array of raw encoded SMS bearer data.
+     */
+    public static byte[] encode(BearerData bData) {
+        bData.hasUserDataHeader = ((bData.userData != null) &&
+                (bData.userData.userDataHeader != null));
+        try {
+            BitwiseOutputStream outStream = new BitwiseOutputStream(200);
+            outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
+            encodeMessageId(bData, outStream);
+            if (bData.userData != null) {
+                outStream.write(8, SUBPARAM_USER_DATA);
+                encodeUserData(bData, outStream);
+            }
+            if (bData.callbackNumber != null) {
+                outStream.write(8, SUBPARAM_CALLBACK_NUMBER);
+                encodeCallbackNumber(bData, outStream);
+            }
+            if (bData.userAckReq || bData.deliveryAckReq || bData.readAckReq || bData.reportReq) {
+                outStream.write(8, SUBPARAM_REPLY_OPTION);
+                encodeReplyOption(bData, outStream);
+            }
+            if (bData.numberOfMessages != 0) {
+                outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES);
+                encodeMsgCount(bData, outStream);
+            }
+            if (bData.validityPeriodRelativeSet) {
+                outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE);
+                encodeValidityPeriodRel(bData, outStream);
+            }
+            if (bData.privacyIndicatorSet) {
+                outStream.write(8, SUBPARAM_PRIVACY_INDICATOR);
+                encodePrivacyIndicator(bData, outStream);
+            }
+            if (bData.languageIndicatorSet) {
+                outStream.write(8, SUBPARAM_LANGUAGE_INDICATOR);
+                encodeLanguageIndicator(bData, outStream);
+            }
+            if (bData.displayModeSet) {
+                outStream.write(8, SUBPARAM_MESSAGE_DISPLAY_MODE);
+                encodeDisplayMode(bData, outStream);
+            }
+            if (bData.priorityIndicatorSet) {
+                outStream.write(8, SUBPARAM_PRIORITY_INDICATOR);
+                encodePriorityIndicator(bData, outStream);
+            }
+            if (bData.alertIndicatorSet) {
+                outStream.write(8, SUBPARAM_ALERT_ON_MESSAGE_DELIVERY);
+                encodeMsgDeliveryAlert(bData, outStream);
+            }
+            if (bData.messageStatusSet) {
+                outStream.write(8, SUBPARAM_MESSAGE_STATUS);
+                encodeMsgStatus(bData, outStream);
+            }
+            if (bData.serviceCategoryProgramResults != null) {
+                outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS);
+                encodeScpResults(bData, outStream);
+            }
+            return outStream.toByteArray();
+        } catch (BitwiseOutputStream.AccessException ex) {
+            Rlog.e(LOG_TAG, "BearerData encode failed: " + ex);
+        } catch (CodingException ex) {
+            Rlog.e(LOG_TAG, "BearerData encode failed: " + ex);
+        }
+        return null;
+   }
+
+    private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 3 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.messageType = inStream.read(4);
+            bData.messageId = inStream.read(8) << 8;
+            bData.messageId |= inStream.read(8);
+            bData.hasUserDataHeader = (inStream.read(1) == 1);
+            inStream.skip(3);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static boolean decodeReserved(
+            BearerData bData, BitwiseInputStream inStream, int subparamId)
+        throws BitwiseInputStream.AccessException, CodingException
+    {
+        boolean decodeSuccess = false;
+        int subparamLen = inStream.read(8); // SUBPARAM_LEN
+        int paramBits = subparamLen * 8;
+        if (paramBits <= inStream.available()) {
+            decodeSuccess = true;
+            inStream.skip(paramBits);
+        }
+        Rlog.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode "
+                + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")");
+        if (!decodeSuccess) {
+            throw new CodingException("RESERVED bearer data subparameter " + subparamId
+                    + " had invalid SUBPARAM_LEN " + subparamLen);
+        }
+
+        return decodeSuccess;
+    }
+
+    private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException
+    {
+        int paramBits = inStream.read(8) * 8;
+        bData.userData = new UserData();
+        bData.userData.msgEncoding = inStream.read(5);
+        bData.userData.msgEncodingSet = true;
+        bData.userData.msgType = 0;
+        int consumedBits = 5;
+        if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+            (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+            bData.userData.msgType = inStream.read(8);
+            consumedBits += 8;
+        }
+        bData.userData.numFields = inStream.read(8);
+        consumedBits += 8;
+        int dataBits = paramBits - consumedBits;
+        bData.userData.payload = inStream.readByteArray(dataBits);
+        return true;
+    }
+
+    private static String decodeUtf8(byte[] data, int offset, int numFields)
+        throws CodingException
+    {
+        return decodeCharset(data, offset, numFields, 1, "UTF-8");
+    }
+
+    private static String decodeUtf16(byte[] data, int offset, int numFields)
+        throws CodingException
+    {
+        // Subtract header and possible padding byte (at end) from num fields.
+        int padding = offset % 2;
+        numFields -= (offset + padding) / 2;
+        return decodeCharset(data, offset, numFields, 2, "utf-16be");
+    }
+
+    private static String decodeCharset(byte[] data, int offset, int numFields, int width,
+            String charset) throws CodingException
+    {
+        if (numFields < 0 || (numFields * width + offset) > data.length) {
+            // Try to decode the max number of characters in payload
+            int padding = offset % width;
+            int maxNumFields = (data.length - offset - padding) / width;
+            if (maxNumFields < 0) {
+                throw new CodingException(charset + " decode failed: offset out of range");
+            }
+            Rlog.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
+                    + numFields + " data.length = " + data.length + " maxNumFields = "
+                    + maxNumFields);
+            numFields = maxNumFields;
+        }
+        try {
+            return new String(data, offset, numFields * width, charset);
+        } catch (java.io.UnsupportedEncodingException ex) {
+            throw new CodingException(charset + " decode failed: " + ex);
+        }
+    }
+
+    private static String decode7bitAscii(byte[] data, int offset, int numFields)
+        throws CodingException
+    {
+        try {
+            offset *= 8;
+            StringBuffer strBuf = new StringBuffer(numFields);
+            BitwiseInputStream inStream = new BitwiseInputStream(data);
+            int wantedBits = (offset * 8) + (numFields * 7);
+            if (inStream.available() < wantedBits) {
+                throw new CodingException("insufficient data (wanted " + wantedBits +
+                                          " bits, but only have " + inStream.available() + ")");
+            }
+            inStream.skip(offset);
+            for (int i = 0; i < numFields; i++) {
+                int charCode = inStream.read(7);
+                if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) &&
+                        (charCode <= UserData.ASCII_MAP_MAX_INDEX)) {
+                    strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]);
+                } else if (charCode == UserData.ASCII_NL_INDEX) {
+                    strBuf.append('\n');
+                } else if (charCode == UserData.ASCII_CR_INDEX) {
+                    strBuf.append('\r');
+                } else {
+                    /* For other charCodes, they are unprintable, and so simply use SPACE. */
+                    strBuf.append(' ');
+                }
+            }
+            return strBuf.toString();
+        } catch (BitwiseInputStream.AccessException ex) {
+            throw new CodingException("7bit ASCII decode failed: " + ex);
+        }
+    }
+
+    private static String decode7bitGsm(byte[] data, int offset, int numFields)
+        throws CodingException
+    {
+        // Start reading from the next 7-bit aligned boundary after offset.
+        int offsetBits = offset * 8;
+        int offsetSeptets = (offsetBits + 6) / 7;
+        numFields -= offsetSeptets;
+        int paddingBits = (offsetSeptets * 7) - offsetBits;
+        String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits,
+                0, 0);
+        if (result == null) {
+            throw new CodingException("7bit GSM decoding failed");
+        }
+        return result;
+    }
+
+    private static String decodeLatin(byte[] data, int offset, int numFields)
+        throws CodingException
+    {
+        return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
+    }
+
+    private static String decodeShiftJis(byte[] data, int offset, int numFields)
+        throws CodingException
+    {
+        return decodeCharset(data, offset, numFields, 1, "Shift_JIS");
+    }
+
+    private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)
+        throws CodingException
+    {
+        int offset = 0;
+        if (hasUserDataHeader) {
+            int udhLen = userData.payload[0] & 0x00FF;
+            offset += udhLen + 1;
+            byte[] headerData = new byte[udhLen];
+            System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
+            userData.userDataHeader = SmsHeader.fromByteArray(headerData);
+        }
+        switch (userData.msgEncoding) {
+        case UserData.ENCODING_OCTET:
+            /*
+            *  Octet decoding depends on the carrier service.
+            */
+            boolean decodingtypeUTF8 = Resources.getSystem()
+                    .getBoolean(com.android.internal.R.bool.config_sms_utf8_support);
+
+            // Strip off any padding bytes, meaning any differences between the length of the
+            // array and the target length specified by numFields.  This is to avoid any
+            // confusion by code elsewhere that only considers the payload array length.
+            byte[] payload = new byte[userData.numFields];
+            int copyLen = userData.numFields < userData.payload.length
+                    ? userData.numFields : userData.payload.length;
+
+            System.arraycopy(userData.payload, 0, payload, 0, copyLen);
+            userData.payload = payload;
+
+            if (!decodingtypeUTF8) {
+                // There are many devices in the market that send 8bit text sms (latin encoded) as
+                // octet encoded.
+                userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+            } else {
+                userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
+            }
+            break;
+
+        case UserData.ENCODING_IA5:
+        case UserData.ENCODING_7BIT_ASCII:
+            userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
+            break;
+        case UserData.ENCODING_UNICODE_16:
+            userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields);
+            break;
+        case UserData.ENCODING_GSM_7BIT_ALPHABET:
+            userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields);
+            break;
+        case UserData.ENCODING_LATIN:
+            userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+            break;
+        case UserData.ENCODING_SHIFT_JIS:
+            userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields);
+            break;
+        default:
+            throw new CodingException("unsupported user data encoding ("
+                                      + userData.msgEncoding + ")");
+        }
+    }
+
+    /**
+     * IS-91 Voice Mail message decoding
+     * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+     * (For character encodings, see TIA/EIA/IS-91, Annex B)
+     *
+     * Protocol Summary: The user data payload may contain 3-14
+     * characters.  The first two characters are parsed as a number
+     * and indicate the number of voicemails.  The third character is
+     * either a SPACE or '!' to indicate normal or urgent priority,
+     * respectively.  Any following characters are treated as normal
+     * text user data payload.
+     *
+     * Note that the characters encoding is 6-bit packed.
+     */
+    private static void decodeIs91VoicemailStatus(BearerData bData)
+        throws BitwiseInputStream.AccessException, CodingException
+    {
+        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+        int dataLen = inStream.available() / 6;  // 6-bit packed character encoding.
+        int numFields = bData.userData.numFields;
+        if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
+            throw new CodingException("IS-91 voicemail status decoding failed");
+        }
+        try {
+            StringBuffer strbuf = new StringBuffer(dataLen);
+            while (inStream.available() >= 6) {
+                strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
+            }
+            String data = strbuf.toString();
+            bData.numberOfMessages = Integer.parseInt(data.substring(0, 2));
+            char prioCode = data.charAt(2);
+            if (prioCode == ' ') {
+                bData.priority = PRIORITY_NORMAL;
+            } else if (prioCode == '!') {
+                bData.priority = PRIORITY_URGENT;
+            } else {
+                throw new CodingException("IS-91 voicemail status decoding failed: " +
+                        "illegal priority setting (" + prioCode + ")");
+            }
+            bData.priorityIndicatorSet = true;
+            bData.userData.payloadStr = data.substring(3, numFields - 3);
+       } catch (java.lang.NumberFormatException ex) {
+            throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
+        } catch (java.lang.IndexOutOfBoundsException ex) {
+            throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
+        }
+    }
+
+    /**
+     * IS-91 Short Message decoding
+     * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+     * (For character encodings, see TIA/EIA/IS-91, Annex B)
+     *
+     * Protocol Summary: The user data payload may contain 1-14
+     * characters, which are treated as normal text user data payload.
+     * Note that the characters encoding is 6-bit packed.
+     */
+    private static void decodeIs91ShortMessage(BearerData bData)
+        throws BitwiseInputStream.AccessException, CodingException
+    {
+        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+        int dataLen = inStream.available() / 6;  // 6-bit packed character encoding.
+        int numFields = bData.userData.numFields;
+        // dataLen may be > 14 characters due to octet padding
+        if ((numFields > 14) || (dataLen < numFields)) {
+            throw new CodingException("IS-91 short message decoding failed");
+        }
+        StringBuffer strbuf = new StringBuffer(dataLen);
+        for (int i = 0; i < numFields; i++) {
+            strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
+        }
+        bData.userData.payloadStr = strbuf.toString();
+    }
+
+    /**
+     * IS-91 CLI message (callback number) decoding
+     * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+     *
+     * Protocol Summary: The data payload may contain 1-32 digits,
+     * encoded using standard 4-bit DTMF, which are treated as a
+     * callback number.
+     */
+    private static void decodeIs91Cli(BearerData bData) throws CodingException {
+        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+        int dataLen = inStream.available() / 4;  // 4-bit packed DTMF digit encoding.
+        int numFields = bData.userData.numFields;
+        if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
+            throw new CodingException("IS-91 voicemail status decoding failed");
+        }
+        CdmaSmsAddress addr = new CdmaSmsAddress();
+        addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF;
+        addr.origBytes = bData.userData.payload;
+        addr.numberOfDigits = (byte)numFields;
+        decodeSmsAddress(addr);
+        bData.callbackNumber = addr;
+    }
+
+    private static void decodeIs91(BearerData bData)
+        throws BitwiseInputStream.AccessException, CodingException
+    {
+        switch (bData.userData.msgType) {
+        case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS:
+            decodeIs91VoicemailStatus(bData);
+            break;
+        case UserData.IS91_MSG_TYPE_CLI:
+            decodeIs91Cli(bData);
+            break;
+        case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL:
+        case UserData.IS91_MSG_TYPE_SHORT_MESSAGE:
+            decodeIs91ShortMessage(bData);
+            break;
+        default:
+            throw new CodingException("unsupported IS-91 message type (" +
+                    bData.userData.msgType + ")");
+        }
+    }
+
+    private static boolean decodeReplyOption(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.userAckReq     = (inStream.read(1) == 1);
+            bData.deliveryAckReq = (inStream.read(1) == 1);
+            bData.readAckReq     = (inStream.read(1) == 1);
+            bData.reportReq      = (inStream.read(1) == 1);
+            inStream.skip(4);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "REPLY_OPTION decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static boolean decodeMsgCount(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8));
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static boolean decodeDepositIndex(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 2 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static String decodeDtmfSmsAddress(byte[] rawData, int numFields)
+        throws CodingException
+    {
+        /* DTMF 4-bit digit encoding, defined in at
+         * 3GPP2 C.S005-D, v2.0, table 2.7.1.3.2.4-4 */
+        StringBuffer strBuf = new StringBuffer(numFields);
+        for (int i = 0; i < numFields; i++) {
+            int val = 0x0F & (rawData[i / 2] >>> (4 - ((i % 2) * 4)));
+            if ((val >= 1) && (val <= 9)) strBuf.append(Integer.toString(val, 10));
+            else if (val == 10) strBuf.append('0');
+            else if (val == 11) strBuf.append('*');
+            else if (val == 12) strBuf.append('#');
+            else throw new CodingException("invalid SMS address DTMF code (" + val + ")");
+        }
+        return strBuf.toString();
+    }
+
+    private static void decodeSmsAddress(CdmaSmsAddress addr) throws CodingException {
+        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+            try {
+                /* As specified in 3GPP2 C.S0015-B, v2, 4.5.15 -- actually
+                 * just 7-bit ASCII encoding, with the MSB being zero. */
+                addr.address = new String(addr.origBytes, 0, addr.origBytes.length, "US-ASCII");
+            } catch (java.io.UnsupportedEncodingException ex) {
+                throw new CodingException("invalid SMS address ASCII code");
+            }
+        } else {
+            addr.address = decodeDtmfSmsAddress(addr.origBytes, addr.numberOfDigits);
+        }
+    }
+
+    private static boolean decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException, CodingException
+    {
+        final int EXPECTED_PARAM_SIZE = 1 * 8; //at least
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits < EXPECTED_PARAM_SIZE) {
+            inStream.skip(paramBits);
+            return false;
+        }
+        CdmaSmsAddress addr = new CdmaSmsAddress();
+        addr.digitMode = inStream.read(1);
+        byte fieldBits = 4;
+        byte consumedBits = 1;
+        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+            addr.ton = inStream.read(3);
+            addr.numberPlan = inStream.read(4);
+            fieldBits = 8;
+            consumedBits += 7;
+        }
+        addr.numberOfDigits = inStream.read(8);
+        consumedBits += 8;
+        int remainingBits = paramBits - consumedBits;
+        int dataBits = addr.numberOfDigits * fieldBits;
+        int paddingBits = remainingBits - dataBits;
+        if (remainingBits < dataBits) {
+            throw new CodingException("CALLBACK_NUMBER subparam encoding size error (" +
+                                      "remainingBits + " + remainingBits + ", dataBits + " +
+                                      dataBits + ", paddingBits + " + paddingBits + ")");
+        }
+        addr.origBytes = inStream.readByteArray(dataBits);
+        inStream.skip(paddingBits);
+        decodeSmsAddress(addr);
+        bData.callbackNumber = addr;
+        return true;
+    }
+
+    private static boolean decodeMsgStatus(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.errorClass = inStream.read(2);
+            bData.messageStatus = inStream.read(6);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        bData.messageStatusSet = decodeSuccess;
+        return decodeSuccess;
+    }
+
+    private static boolean decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 6 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static boolean decodeValidityAbs(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 6 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static boolean decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 6 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray(
+                    inStream.readByteArray(6 * 8));
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        return decodeSuccess;
+    }
+
+    private static boolean decodeValidityRel(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.deferredDeliveryTimeRelative = inStream.read(8);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        bData.deferredDeliveryTimeRelativeSet = decodeSuccess;
+        return decodeSuccess;
+    }
+
+    private static boolean decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.validityPeriodRelative = inStream.read(8);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        bData.validityPeriodRelativeSet = decodeSuccess;
+        return decodeSuccess;
+    }
+
+    private static boolean decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.privacy = inStream.read(2);
+            inStream.skip(6);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        bData.privacyIndicatorSet = decodeSuccess;
+        return decodeSuccess;
+    }
+
+    private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.language = inStream.read(8);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        bData.languageIndicatorSet = decodeSuccess;
+        return decodeSuccess;
+    }
+
+    private static boolean decodeDisplayMode(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.displayMode = inStream.read(2);
+            inStream.skip(6);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "DISPLAY_MODE decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        bData.displayModeSet = decodeSuccess;
+        return decodeSuccess;
+    }
+
+    private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.priority = inStream.read(2);
+            inStream.skip(6);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        bData.priorityIndicatorSet = decodeSuccess;
+        return decodeSuccess;
+    }
+
+    private static boolean decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.alert = inStream.read(2);
+            inStream.skip(6);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        bData.alertIndicatorSet = decodeSuccess;
+        return decodeSuccess;
+    }
+
+    private static boolean decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream)
+        throws BitwiseInputStream.AccessException {
+        final int EXPECTED_PARAM_SIZE = 1 * 8;
+        boolean decodeSuccess = false;
+        int paramBits = inStream.read(8) * 8;
+        if (paramBits >= EXPECTED_PARAM_SIZE) {
+            paramBits -= EXPECTED_PARAM_SIZE;
+            decodeSuccess = true;
+            bData.userResponseCode = inStream.read(8);
+        }
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ")");
+        }
+        inStream.skip(paramBits);
+        bData.userResponseCodeSet = decodeSuccess;
+        return decodeSuccess;
+    }
+
+    private static boolean decodeServiceCategoryProgramData(BearerData bData,
+            BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException
+    {
+        if (inStream.available() < 13) {
+            throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+                    + inStream.available() + " bits available");
+        }
+
+        int paramBits = inStream.read(8) * 8;
+        int msgEncoding = inStream.read(5);
+        paramBits -= 5;
+
+        if (inStream.available() < paramBits) {
+            throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+                    + inStream.available() + " bits available (" + paramBits + " bits expected)");
+        }
+
+        ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>();
+
+        final int CATEGORY_FIELD_MIN_SIZE = 6 * 8;
+        boolean decodeSuccess = false;
+        while (paramBits >= CATEGORY_FIELD_MIN_SIZE) {
+            int operation = inStream.read(4);
+            int category = (inStream.read(8) << 8) | inStream.read(8);
+            int language = inStream.read(8);
+            int maxMessages = inStream.read(8);
+            int alertOption = inStream.read(4);
+            int numFields = inStream.read(8);
+            paramBits -= CATEGORY_FIELD_MIN_SIZE;
+
+            int textBits = getBitsForNumFields(msgEncoding, numFields);
+            if (paramBits < textBits) {
+                throw new CodingException("category name is " + textBits + " bits in length,"
+                        + " but there are only " + paramBits + " bits available");
+            }
+
+            UserData userData = new UserData();
+            userData.msgEncoding = msgEncoding;
+            userData.msgEncodingSet = true;
+            userData.numFields = numFields;
+            userData.payload = inStream.readByteArray(textBits);
+            paramBits -= textBits;
+
+            decodeUserDataPayload(userData, false);
+            String categoryName = userData.payloadStr;
+            CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category,
+                    language, maxMessages, alertOption, categoryName);
+            programDataList.add(programData);
+
+            decodeSuccess = true;
+        }
+
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Rlog.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ')');
+        }
+
+        inStream.skip(paramBits);
+        bData.serviceCategoryProgramData = programDataList;
+        return decodeSuccess;
+    }
+
+    private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
+        switch (serviceCategory) {
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
+                return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
+                return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
+                return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
+                return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+            default:
+                return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+        }
+    }
+
+    /**
+     * Calculates the number of bits to read for the specified number of encoded characters.
+     * @param msgEncoding the message encoding to use
+     * @param numFields the number of characters to read. For Shift-JIS and Korean encodings,
+     *  this is the number of bytes to read.
+     * @return the number of bits to read from the stream
+     * @throws CodingException if the specified encoding is not supported
+     */
+    private static int getBitsForNumFields(int msgEncoding, int numFields)
+            throws CodingException {
+        switch (msgEncoding) {
+            case UserData.ENCODING_OCTET:
+            case UserData.ENCODING_SHIFT_JIS:
+            case UserData.ENCODING_KOREAN:
+            case UserData.ENCODING_LATIN:
+            case UserData.ENCODING_LATIN_HEBREW:
+                return numFields * 8;
+
+            case UserData.ENCODING_IA5:
+            case UserData.ENCODING_7BIT_ASCII:
+            case UserData.ENCODING_GSM_7BIT_ALPHABET:
+                return numFields * 7;
+
+            case UserData.ENCODING_UNICODE_16:
+                return numFields * 16;
+
+            default:
+                throw new CodingException("unsupported message encoding (" + msgEncoding + ')');
+        }
+    }
+
+    /**
+     * CMAS message decoding.
+     * (See TIA-1149-0-1, CMAS over CDMA)
+     *
+     * @param serviceCategory is the service category from the SMS envelope
+     */
+    private static void decodeCmasUserData(BearerData bData, int serviceCategory)
+            throws BitwiseInputStream.AccessException, CodingException {
+        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+        if (inStream.available() < 8) {
+            throw new CodingException("emergency CB with no CMAE_protocol_version");
+        }
+        int protocolVersion = inStream.read(8);
+        if (protocolVersion != 0) {
+            throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
+        }
+
+        int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
+        int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+        int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+        int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+        int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+        int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+
+        while (inStream.available() >= 16) {
+            int recordType = inStream.read(8);
+            int recordLen = inStream.read(8);
+            switch (recordType) {
+                case 0:     // Type 0 elements (Alert text)
+                    UserData alertUserData = new UserData();
+                    alertUserData.msgEncoding = inStream.read(5);
+                    alertUserData.msgEncodingSet = true;
+                    alertUserData.msgType = 0;
+
+                    int numFields;                          // number of chars to decode
+                    switch (alertUserData.msgEncoding) {
+                        case UserData.ENCODING_OCTET:
+                        case UserData.ENCODING_LATIN:
+                            numFields = recordLen - 1;      // subtract 1 byte for encoding
+                            break;
+
+                        case UserData.ENCODING_IA5:
+                        case UserData.ENCODING_7BIT_ASCII:
+                        case UserData.ENCODING_GSM_7BIT_ALPHABET:
+                            numFields = ((recordLen * 8) - 5) / 7;  // subtract 5 bits for encoding
+                            break;
+
+                        case UserData.ENCODING_UNICODE_16:
+                            numFields = (recordLen - 1) / 2;
+                            break;
+
+                        default:
+                            numFields = 0;      // unsupported encoding
+                    }
+
+                    alertUserData.numFields = numFields;
+                    alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
+                    decodeUserDataPayload(alertUserData, false);
+                    bData.userData = alertUserData;
+                    break;
+
+                case 1:     // Type 1 elements
+                    category = inStream.read(8);
+                    responseType = inStream.read(8);
+                    severity = inStream.read(4);
+                    urgency = inStream.read(4);
+                    certainty = inStream.read(4);
+                    inStream.skip(recordLen * 8 - 28);
+                    break;
+
+                default:
+                    Rlog.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
+                    inStream.skip(recordLen * 8);
+                    break;
+            }
+        }
+
+        bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
+                urgency, certainty);
+    }
+
+    /**
+     * Create BearerData object from serialized representation.
+     * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+     *
+     * @param smsData byte array of raw encoded SMS bearer data.
+     * @return an instance of BearerData.
+     */
+    public static BearerData decode(byte[] smsData) {
+        return decode(smsData, 0);
+    }
+
+    private static boolean isCmasAlertCategory(int category) {
+        return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
+                && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE;
+    }
+
+    /**
+     * Create BearerData object from serialized representation.
+     * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+     *
+     * @param smsData byte array of raw encoded SMS bearer data.
+     * @param serviceCategory the envelope service category (for CMAS alert handling)
+     * @return an instance of BearerData.
+     */
+    public static BearerData decode(byte[] smsData, int serviceCategory) {
+        try {
+            BitwiseInputStream inStream = new BitwiseInputStream(smsData);
+            BearerData bData = new BearerData();
+            int foundSubparamMask = 0;
+            while (inStream.available() > 0) {
+                int subparamId = inStream.read(8);
+                int subparamIdBit = 1 << subparamId;
+                // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8)
+                // as 32th bit is the max bit in int.
+                // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers:
+                // last defined subparam ID is 23 (00010111 = 0x17 = 23).
+                // Only do duplicate subparam ID check if subparam is within defined value as
+                // reserved subparams are just skipped.
+                if ((foundSubparamMask & subparamIdBit) != 0 &&
+                        (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
+                        subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+                    throw new CodingException("illegal duplicate subparameter (" +
+                                              subparamId + ")");
+                }
+                boolean decodeSuccess;
+                switch (subparamId) {
+                case SUBPARAM_MESSAGE_IDENTIFIER:
+                    decodeSuccess = decodeMessageId(bData, inStream);
+                    break;
+                case SUBPARAM_USER_DATA:
+                    decodeSuccess = decodeUserData(bData, inStream);
+                    break;
+                case SUBPARAM_USER_RESPONSE_CODE:
+                    decodeSuccess = decodeUserResponseCode(bData, inStream);
+                    break;
+                case SUBPARAM_REPLY_OPTION:
+                    decodeSuccess = decodeReplyOption(bData, inStream);
+                    break;
+                case SUBPARAM_NUMBER_OF_MESSAGES:
+                    decodeSuccess = decodeMsgCount(bData, inStream);
+                    break;
+                case SUBPARAM_CALLBACK_NUMBER:
+                    decodeSuccess = decodeCallbackNumber(bData, inStream);
+                    break;
+                case SUBPARAM_MESSAGE_STATUS:
+                    decodeSuccess = decodeMsgStatus(bData, inStream);
+                    break;
+                case SUBPARAM_MESSAGE_CENTER_TIME_STAMP:
+                    decodeSuccess = decodeMsgCenterTimeStamp(bData, inStream);
+                    break;
+                case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE:
+                    decodeSuccess = decodeValidityAbs(bData, inStream);
+                    break;
+                case SUBPARAM_VALIDITY_PERIOD_RELATIVE:
+                    decodeSuccess = decodeValidityRel(bData, inStream);
+                    break;
+                case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE:
+                    decodeSuccess = decodeDeferredDeliveryAbs(bData, inStream);
+                    break;
+                case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE:
+                    decodeSuccess = decodeDeferredDeliveryRel(bData, inStream);
+                    break;
+                case SUBPARAM_PRIVACY_INDICATOR:
+                    decodeSuccess = decodePrivacyIndicator(bData, inStream);
+                    break;
+                case SUBPARAM_LANGUAGE_INDICATOR:
+                    decodeSuccess = decodeLanguageIndicator(bData, inStream);
+                    break;
+                case SUBPARAM_MESSAGE_DISPLAY_MODE:
+                    decodeSuccess = decodeDisplayMode(bData, inStream);
+                    break;
+                case SUBPARAM_PRIORITY_INDICATOR:
+                    decodeSuccess = decodePriorityIndicator(bData, inStream);
+                    break;
+                case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY:
+                    decodeSuccess = decodeMsgDeliveryAlert(bData, inStream);
+                    break;
+                case SUBPARAM_MESSAGE_DEPOSIT_INDEX:
+                    decodeSuccess = decodeDepositIndex(bData, inStream);
+                    break;
+                case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA:
+                    decodeSuccess = decodeServiceCategoryProgramData(bData, inStream);
+                    break;
+                default:
+                    decodeSuccess = decodeReserved(bData, inStream, subparamId);
+                }
+                if (decodeSuccess &&
+                        (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
+                        subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+                    foundSubparamMask |= subparamIdBit;
+                }
+            }
+            if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
+                throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
+            }
+            if (bData.userData != null) {
+                if (isCmasAlertCategory(serviceCategory)) {
+                    decodeCmasUserData(bData, serviceCategory);
+                } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
+                    if ((foundSubparamMask ^
+                             (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
+                             (1 << SUBPARAM_USER_DATA))
+                            != 0) {
+                        Rlog.e(LOG_TAG, "IS-91 must occur without extra subparams (" +
+                              foundSubparamMask + ")");
+                    }
+                    decodeIs91(bData);
+                } else {
+                    decodeUserDataPayload(bData.userData, bData.hasUserDataHeader);
+                }
+            }
+            return bData;
+        } catch (BitwiseInputStream.AccessException ex) {
+            Rlog.e(LOG_TAG, "BearerData decode failed: " + ex);
+        } catch (CodingException ex) {
+            Rlog.e(LOG_TAG, "BearerData decode failed: " + ex);
+        }
+        return null;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
new file mode 100644
index 0000000..5f2e561
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
@@ -0,0 +1,228 @@
+/*
+ * 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.telephony.cdma.sms;
+
+import android.util.SparseBooleanArray;
+
+import com.android.internal.telephony.SmsAddress;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.util.HexDump;
+
+public class CdmaSmsAddress extends SmsAddress {
+
+    /**
+     * Digit Mode Indicator is a 1-bit value that indicates whether
+     * the address digits are 4-bit DTMF codes or 8-bit codes.  (See
+     * 3GPP2 C.S0015-B, v2, 3.4.3.3)
+     */
+    static public final int DIGIT_MODE_4BIT_DTMF              = 0x00;
+    static public final int DIGIT_MODE_8BIT_CHAR              = 0x01;
+
+    public int digitMode;
+
+    /**
+     * Number Mode Indicator is 1-bit value that indicates whether the
+     * address type is a data network address or not.  (See 3GPP2
+     * C.S0015-B, v2, 3.4.3.3)
+     */
+    static public final int NUMBER_MODE_NOT_DATA_NETWORK      = 0x00;
+    static public final int NUMBER_MODE_DATA_NETWORK          = 0x01;
+
+    public int numberMode;
+
+    /**
+     * Number Types for data networks.
+     * (See 3GPP2 C.S005-D, table2.7.1.3.2.4-2 for complete table)
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.3 for data network subset)
+     * NOTE: value is stored in the parent class ton field.
+     */
+    static public final int TON_UNKNOWN                   = 0x00;
+    static public final int TON_INTERNATIONAL_OR_IP       = 0x01;
+    static public final int TON_NATIONAL_OR_EMAIL         = 0x02;
+    static public final int TON_NETWORK                   = 0x03;
+    static public final int TON_SUBSCRIBER                = 0x04;
+    static public final int TON_ALPHANUMERIC              = 0x05;
+    static public final int TON_ABBREVIATED               = 0x06;
+    static public final int TON_RESERVED                  = 0x07;
+
+    /**
+     * Maximum lengths for fields as defined in ril_cdma_sms.h.
+     */
+    static public final int SMS_ADDRESS_MAX          =  36;
+    static public final int SMS_SUBADDRESS_MAX       =  36;
+
+    /**
+     * This field shall be set to the number of address digits
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+     */
+    public int numberOfDigits;
+
+    /**
+     * Numbering Plan identification is a 0 or 4-bit value that
+     * indicates which numbering plan identification is set.  (See
+     * 3GPP2, C.S0015-B, v2, 3.4.3.3 and C.S005-D, table2.7.1.3.2.4-3)
+     */
+    static public final int NUMBERING_PLAN_UNKNOWN           = 0x0;
+    static public final int NUMBERING_PLAN_ISDN_TELEPHONY    = 0x1;
+    //static protected final int NUMBERING_PLAN_DATA              = 0x3;
+    //static protected final int NUMBERING_PLAN_TELEX             = 0x4;
+    //static protected final int NUMBERING_PLAN_PRIVATE           = 0x9;
+
+    public int numberPlan;
+
+    /**
+     * NOTE: the parsed string address and the raw byte array values
+     * are stored in the parent class address and origBytes fields,
+     * respectively.
+     */
+
+    public CdmaSmsAddress(){
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("CdmaSmsAddress ");
+        builder.append("{ digitMode=" + digitMode);
+        builder.append(", numberMode=" + numberMode);
+        builder.append(", numberPlan=" + numberPlan);
+        builder.append(", numberOfDigits=" + numberOfDigits);
+        builder.append(", ton=" + ton);
+        builder.append(", address=\"" + address + "\"");
+        builder.append(", origBytes=" + HexDump.toHexString(origBytes));
+        builder.append(" }");
+        return builder.toString();
+    }
+
+    /*
+     * TODO(cleanup): Refactor the parsing for addresses to better
+     * share code and logic with GSM.  Also, gather all DTMF/BCD
+     * processing code in one place.
+     */
+
+    private static byte[] parseToDtmf(String address) {
+        int digits = address.length();
+        byte[] result = new byte[digits];
+        for (int i = 0; i < digits; i++) {
+            char c = address.charAt(i);
+            int val = 0;
+            if ((c >= '1') && (c <= '9')) val = c - '0';
+            else if (c == '0') val = 10;
+            else if (c == '*') val = 11;
+            else if (c == '#') val = 12;
+            else return null;
+            result[i] = (byte)val;
+        }
+        return result;
+    }
+
+    private static final char[] numericCharsDialable = {
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#'
+    };
+
+    private static final char[] numericCharsSugar = {
+        '(', ')', ' ', '-', '+', '.', '/', '\\'
+    };
+
+    private static final SparseBooleanArray numericCharDialableMap = new SparseBooleanArray (
+            numericCharsDialable.length + numericCharsSugar.length);
+    static {
+        for (int i = 0; i < numericCharsDialable.length; i++) {
+            numericCharDialableMap.put(numericCharsDialable[i], true);
+        }
+        for (int i = 0; i < numericCharsSugar.length; i++) {
+            numericCharDialableMap.put(numericCharsSugar[i], false);
+        }
+    }
+
+    /**
+     * Given a numeric address string, return the string without
+     * syntactic sugar, meaning parens, spaces, hyphens/minuses, or
+     * plus signs.  If the input string contains non-numeric
+     * non-punctuation characters, return null.
+     */
+    private static String filterNumericSugar(String address) {
+        StringBuilder builder = new StringBuilder();
+        int len = address.length();
+        for (int i = 0; i < len; i++) {
+            char c = address.charAt(i);
+            int mapIndex = numericCharDialableMap.indexOfKey(c);
+            if (mapIndex < 0) return null;
+            if (! numericCharDialableMap.valueAt(mapIndex)) continue;
+            builder.append(c);
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Given a string, return the string without whitespace,
+     * including CR/LF.
+     */
+    private static String filterWhitespace(String address) {
+        StringBuilder builder = new StringBuilder();
+        int len = address.length();
+        for (int i = 0; i < len; i++) {
+            char c = address.charAt(i);
+            if ((c == ' ') || (c == '\r') || (c == '\n') || (c == '\t')) continue;
+            builder.append(c);
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Given a string, create a corresponding CdmaSmsAddress object.
+     *
+     * The result will be null if the input string is not
+     * representable using printable ASCII.
+     *
+     * For numeric addresses, the string is cleaned up by removing
+     * common punctuation.  For alpha addresses, the string is cleaned
+     * up by removing whitespace.
+     */
+    public static CdmaSmsAddress parse(String address) {
+        CdmaSmsAddress addr = new CdmaSmsAddress();
+        addr.address = address;
+        addr.ton = CdmaSmsAddress.TON_UNKNOWN;
+        byte[] origBytes = null;
+        String filteredAddr = filterNumericSugar(address);
+        if (filteredAddr != null) {
+            origBytes = parseToDtmf(filteredAddr);
+        }
+        if (origBytes != null) {
+            addr.digitMode = DIGIT_MODE_4BIT_DTMF;
+            addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+            if (address.indexOf('+') != -1) {
+                addr.ton = TON_INTERNATIONAL_OR_IP;
+            }
+        } else {
+            filteredAddr = filterWhitespace(address);
+            origBytes = UserData.stringToAscii(filteredAddr);
+            if (origBytes == null) {
+                return null;
+            }
+            addr.digitMode = DIGIT_MODE_8BIT_CHAR;
+            addr.numberMode = NUMBER_MODE_DATA_NETWORK;
+            if (address.indexOf('@') != -1) {
+                addr.ton = TON_NATIONAL_OR_EMAIL;
+            }
+        }
+        addr.origBytes = origBytes;
+        addr.numberOfDigits = origBytes.length;
+        return addr;
+    }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java
new file mode 100644
index 0000000..0d5b502
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project. All rights reserved.
+ *
+ * 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.telephony.cdma.sms;
+
+public class CdmaSmsSubaddress {
+    public int type;
+
+    public byte odd;
+
+    public byte[] origBytes;
+}
+
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java
new file mode 100644
index 0000000..f73df56
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java
@@ -0,0 +1,130 @@
+/*
+ * 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.telephony.cdma.sms;
+
+
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+
+public final class SmsEnvelope {
+    /**
+     * Message Types
+     * (See 3GPP2 C.S0015-B 3.4.1)
+     */
+    static public final int MESSAGE_TYPE_POINT_TO_POINT   = 0x00;
+    static public final int MESSAGE_TYPE_BROADCAST        = 0x01;
+    static public final int MESSAGE_TYPE_ACKNOWLEDGE      = 0x02;
+
+    /**
+     * Supported Teleservices
+     * (See 3GPP2 N.S0005 and TIA-41)
+     */
+    static public final int TELESERVICE_NOT_SET           = 0x0000;
+    static public final int TELESERVICE_WMT               = 0x1002;
+    static public final int TELESERVICE_VMN               = 0x1003;
+    static public final int TELESERVICE_WAP               = 0x1004;
+    static public final int TELESERVICE_WEMT              = 0x1005;
+    static public final int TELESERVICE_SCPT              = 0x1006;
+
+    /**
+     * The following are defined as extensions to the standard teleservices
+     */
+    // Voice mail notification through Message Waiting Indication in CDMA mode or Analog mode.
+    // Defined in 3GPP2 C.S-0005, 3.7.5.6, an Info Record containing an 8-bit number with the
+    // number of messages waiting, it's used by some CDMA carriers for a voice mail count.
+    static public final int TELESERVICE_MWI               = 0x40000;
+
+    // Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
+    // static final int SERVICE_CATEGORY_EMERGENCY      = 0x0001;
+    //...
+
+    // CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1
+    public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT  = 0x1000;
+    public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT            = 0x1001;
+    public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT             = 0x1002;
+    public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 0x1003;
+    public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE              = 0x1004;
+    public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE       = 0x10ff;
+
+    /**
+     * Provides the type of a SMS message like point to point, broadcast or acknowledge
+     */
+    public int messageType;
+
+    /**
+     * The 16-bit Teleservice parameter identifies which upper layer service access point is sending
+     * or receiving the message.
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.1)
+     */
+    public int teleService = TELESERVICE_NOT_SET;
+
+    /**
+     * The 16-bit service category parameter identifies the type of service provided
+     * by the SMS message.
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.2)
+     */
+    public int serviceCategory;
+
+    /**
+     * The origination address identifies the originator of the SMS message.
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+     */
+    public CdmaSmsAddress origAddress;
+
+    /**
+     * The destination address identifies the target of the SMS message.
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+     */
+    public CdmaSmsAddress destAddress;
+
+    /**
+     * The origination subaddress identifies the originator of the SMS message.
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.4)
+     */
+    public CdmaSmsSubaddress origSubaddress;
+
+    /**
+     * The 6-bit bearer reply parameter is used to request the return of a
+     * SMS Acknowledge Message.
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.5)
+     */
+    public int bearerReply;
+
+    /**
+     * Cause Code values:
+     * The cause code parameters are an indication whether an SMS error has occurred and if so,
+     * whether the condition is considered temporary or permanent.
+     * ReplySeqNo 6-bit value,
+     * ErrorClass 2-bit value,
+     * CauseCode 0-bit or 8-bit value
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.6)
+     */
+    public byte replySeqNo;
+    public byte errorClass;
+    public byte causeCode;
+
+    /**
+     * encoded bearer data
+     * (See 3GPP2 C.S0015-B, v2, 3.4.3.7)
+     */
+    public byte[] bearerData;
+
+    public SmsEnvelope() {
+        // nothing to see here
+    }
+
+}
+
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
new file mode 100644
index 0000000..629173d
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -0,0 +1,968 @@
+/*
+ * 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.telephony.cdma;
+
+import android.os.Parcel;
+import android.os.SystemProperties;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.telephony.Rlog;
+import android.util.Log;
+import android.text.TextUtils;
+import android.content.res.Resources;
+
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsAddress;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.HexDump;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * TODO(cleanup): these constants are disturbing... are they not just
+ * different interpretations on one number?  And if we did not have
+ * terrible class name overlap, they would not need to be directly
+ * imported like this.  The class in this file could just as well be
+ * named CdmaSmsMessage, could it not?
+ */
+
+/**
+ * TODO(cleanup): internally returning null in many places makes
+ * debugging very hard (among many other reasons) and should be made
+ * more meaningful (replaced with exceptions for example).  Null
+ * returns should only occur at the very outside of the module/class
+ * scope.
+ */
+
+/**
+ * A Short Message Service message.
+ *
+ */
+public class SmsMessage extends SmsMessageBase {
+    static final String LOG_TAG = "SmsMessage";
+    static private final String LOGGABLE_TAG = "CDMA:SMS";
+    private static final boolean VDBG = false;
+
+    private final static byte TELESERVICE_IDENTIFIER                    = 0x00;
+    private final static byte SERVICE_CATEGORY                          = 0x01;
+    private final static byte ORIGINATING_ADDRESS                       = 0x02;
+    private final static byte ORIGINATING_SUB_ADDRESS                   = 0x03;
+    private final static byte DESTINATION_ADDRESS                       = 0x04;
+    private final static byte DESTINATION_SUB_ADDRESS                   = 0x05;
+    private final static byte BEARER_REPLY_OPTION                       = 0x06;
+    private final static byte CAUSE_CODES                               = 0x07;
+    private final static byte BEARER_DATA                               = 0x08;
+
+    /**
+     *  Status of a previously submitted SMS.
+     *  This field applies to SMS Delivery Acknowledge messages. 0 indicates success;
+     *  Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0.
+     *  See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values.
+     */
+    private int status;
+
+    /** Specifies if a return of an acknowledgment is requested for send SMS */
+    private static final int RETURN_NO_ACK  = 0;
+    private static final int RETURN_ACK     = 1;
+
+    private SmsEnvelope mEnvelope;
+    private BearerData mBearerData;
+
+    /** @hide */
+    public SmsMessage(SmsAddress addr, SmsEnvelope env) {
+        mOriginatingAddress = addr;
+        mEnvelope = env;
+        createPdu();
+    }
+
+    public SmsMessage() {}
+
+    public static class SubmitPdu extends SubmitPduBase {
+    }
+
+    /**
+     * Create an SmsMessage from a raw PDU.
+     * Note: In CDMA the PDU is just a byte representation of the received Sms.
+     */
+    public static SmsMessage createFromPdu(byte[] pdu) {
+        SmsMessage msg = new SmsMessage();
+
+        try {
+            msg.parsePdu(pdu);
+            return msg;
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+            return null;
+        } catch (OutOfMemoryError e) {
+            Log.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
+            return null;
+        }
+    }
+
+    /**
+     * Create an SmsMessage from an SMS EF record.
+     *
+     * @param index Index of SMS record. This should be index in ArrayList
+     *              returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 1.
+     * @param data Record data.
+     * @return An SmsMessage representing the record.
+     *
+     * @hide
+     */
+    public static SmsMessage createFromEfRecord(int index, byte[] data) {
+        try {
+            SmsMessage msg = new SmsMessage();
+
+            msg.mIndexOnIcc = index;
+
+            // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
+            // or STORED_UNSENT
+            // See 3GPP2 C.S0023 3.4.27
+            if ((data[0] & 1) == 0) {
+                Rlog.w(LOG_TAG, "SMS parsing failed: Trying to parse a free record");
+                return null;
+            } else {
+                msg.mStatusOnIcc = data[0] & 0x07;
+            }
+
+            // Second byte is the MSG_LEN, length of the message
+            // See 3GPP2 C.S0023 3.4.27
+            int size = data[1];
+
+            // Note: Data may include trailing FF's.  That's OK; message
+            // should still parse correctly.
+            byte[] pdu = new byte[size];
+            System.arraycopy(data, 2, pdu, 0, size);
+            // the message has to be parsed before it can be displayed
+            // see gsm.SmsMessage
+            msg.parsePduFromEfRecord(pdu);
+            return msg;
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+            return null;
+        }
+
+    }
+
+    /**
+     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+     */
+    public static int getTPLayerLengthForPDU(String pdu) {
+        Rlog.w(LOG_TAG, "getTPLayerLengthForPDU: is not supported in CDMA mode.");
+        return 0;
+    }
+
+    /**
+     * TODO(cleanup): why do getSubmitPdu methods take an scAddr input
+     * and do nothing with it?  GSM allows us to specify a SC (eg,
+     * when responding to an SMS that explicitly requests the response
+     * is sent to a specific SC), or pass null to use the default
+     * value.  Is there no similar notion in CDMA? Or do we just not
+     * have it hooked up?
+     */
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message
+     *
+     * @param scAddr                Service Centre address.  Null means use default.
+     * @param destAddr              Address of the recipient.
+     * @param message               String representation of the message payload.
+     * @param statusReportRequested Indicates whether a report is requested for this message.
+     * @param smsHeader             Array containing the data for the User Data Header, preceded
+     *                              by the Element Identifiers.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
+            boolean statusReportRequested, SmsHeader smsHeader) {
+
+        /**
+         * TODO(cleanup): Do we really want silent failure like this?
+         * Would it not be much more reasonable to make sure we don't
+         * call this function if we really want nothing done?
+         */
+        if (message == null || destAddr == null) {
+            return null;
+        }
+
+        UserData uData = new UserData();
+        uData.payloadStr = message;
+        uData.userDataHeader = smsHeader;
+        return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a data message to a destination address and port.
+     *
+     * @param scAddr Service Centre address. null == use default
+     * @param destAddr the address of the destination for the message
+     * @param destPort the port to deliver the message to at the
+     *        destination
+     * @param data the data for the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort,
+            byte[] data, boolean statusReportRequested) {
+
+        /**
+         * TODO(cleanup): this is not a general-purpose SMS creation
+         * method, but rather something specialized to messages
+         * containing OCTET encoded (meaning non-human-readable) user
+         * data.  The name should reflect that, and not just overload.
+         */
+
+        SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+        portAddrs.destPort = destPort;
+        portAddrs.origPort = 0;
+        portAddrs.areEightBits = false;
+
+        SmsHeader smsHeader = new SmsHeader();
+        smsHeader.portAddrs = portAddrs;
+
+        UserData uData = new UserData();
+        uData.userDataHeader = smsHeader;
+        uData.msgEncoding = UserData.ENCODING_OCTET;
+        uData.msgEncodingSet = true;
+        uData.payload = data;
+
+        return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+     *
+     * @param destAddr the address of the destination for the message
+     * @param userData the data for the message
+     * @param statusReportRequested Indicates whether a report is requested for this message.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
+            boolean statusReportRequested) {
+        return privateGetSubmitPdu(destAddr, statusReportRequested, userData);
+    }
+
+    /**
+     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+     */
+    @Override
+    public int getProtocolIdentifier() {
+        Rlog.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode.");
+        // (3GPP TS 23.040): "no interworking, but SME to SME protocol":
+        return 0;
+    }
+
+    /**
+     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+     */
+    @Override
+    public boolean isReplace() {
+        Rlog.w(LOG_TAG, "isReplace: is not supported in CDMA mode.");
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+     */
+    @Override
+    public boolean isCphsMwiMessage() {
+        Rlog.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode.");
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isMWIClearMessage() {
+        return ((mBearerData != null) && (mBearerData.numberOfMessages == 0));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isMWISetMessage() {
+        return ((mBearerData != null) && (mBearerData.numberOfMessages > 0));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isMwiDontStore() {
+        return ((mBearerData != null) &&
+                (mBearerData.numberOfMessages > 0) &&
+                (mBearerData.userData == null));
+    }
+
+    /**
+     * Returns the status for a previously submitted message.
+     * For not interfering with status codes from GSM, this status code is
+     * shifted to the bits 31-16.
+     */
+    @Override
+    public int getStatus() {
+        return (status << 16);
+    }
+
+    /** Return true iff the bearer data message type is DELIVERY_ACK. */
+    @Override
+    public boolean isStatusReportMessage() {
+        return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK);
+    }
+
+    /**
+     * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+     */
+    @Override
+    public boolean isReplyPathPresent() {
+        Rlog.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode.");
+        return false;
+    }
+
+    /**
+     * Calculate the number of septets needed to encode the message.
+     *
+     * @param messageBody the message to encode
+     * @param use7bitOnly ignore (but still count) illegal characters if true
+     * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
+     * @return TextEncodingDetails
+     */
+    public static TextEncodingDetails calculateLength(CharSequence messageBody,
+            boolean use7bitOnly, boolean isEntireMsg) {
+        CharSequence newMsgBody = null;
+        Resources r = Resources.getSystem();
+        if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+            newMsgBody  = Sms7BitEncodingTranslator.translate(messageBody);
+        }
+        if (TextUtils.isEmpty(newMsgBody)) {
+            newMsgBody = messageBody;
+        }
+        return BearerData.calcTextEncodingDetails(newMsgBody, use7bitOnly, isEntireMsg);
+    }
+
+    /**
+     * Returns the teleservice type of the message.
+     * @return the teleservice:
+     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET},
+     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT},
+     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT},
+     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN},
+     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP}
+    */
+    public int getTeleService() {
+        return mEnvelope.teleService;
+    }
+
+    /**
+     * Returns the message type of the message.
+     * @return the message type:
+     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT},
+     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST},
+     *  {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE},
+    */
+    public int getMessageType() {
+        // NOTE: mEnvelope.messageType is not set correctly for cell broadcasts with some RILs.
+        // Use the service category parameter to detect CMAS and other cell broadcast messages.
+        if (mEnvelope.serviceCategory != 0) {
+            return SmsEnvelope.MESSAGE_TYPE_BROADCAST;
+        } else {
+            return SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+        }
+    }
+
+    /**
+     * Decodes pdu to an empty SMS object.
+     * In the CDMA case the pdu is just an internal byte stream representation
+     * of the SMS Java-object.
+     * @see #createPdu()
+     */
+    private void parsePdu(byte[] pdu) {
+        ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+        DataInputStream dis = new DataInputStream(bais);
+        int length;
+        int bearerDataLength;
+        SmsEnvelope env = new SmsEnvelope();
+        CdmaSmsAddress addr = new CdmaSmsAddress();
+
+        try {
+            env.messageType = dis.readInt();
+            env.teleService = dis.readInt();
+            env.serviceCategory = dis.readInt();
+
+            addr.digitMode = dis.readByte();
+            addr.numberMode = dis.readByte();
+            addr.ton = dis.readByte();
+            addr.numberPlan = dis.readByte();
+
+            length = dis.readUnsignedByte();
+            addr.numberOfDigits = length;
+
+            // sanity check on the length
+            if (length > pdu.length) {
+                throw new RuntimeException(
+                        "createFromPdu: Invalid pdu, addr.numberOfDigits " + length
+                        + " > pdu len " + pdu.length);
+            }
+            addr.origBytes = new byte[length];
+            dis.read(addr.origBytes, 0, length); // digits
+
+            env.bearerReply = dis.readInt();
+            // CauseCode values:
+            env.replySeqNo = dis.readByte();
+            env.errorClass = dis.readByte();
+            env.causeCode = dis.readByte();
+
+            //encoded BearerData:
+            bearerDataLength = dis.readInt();
+            // sanity check on the length
+            if (bearerDataLength > pdu.length) {
+                throw new RuntimeException(
+                        "createFromPdu: Invalid pdu, bearerDataLength " + bearerDataLength
+                        + " > pdu len " + pdu.length);
+            }
+            env.bearerData = new byte[bearerDataLength];
+            dis.read(env.bearerData, 0, bearerDataLength);
+            dis.close();
+        } catch (IOException ex) {
+            throw new RuntimeException(
+                    "createFromPdu: conversion from byte array to object failed: " + ex, ex);
+        } catch (Exception ex) {
+            Rlog.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex);
+        }
+
+        // link the filled objects to this SMS
+        mOriginatingAddress = addr;
+        env.origAddress = addr;
+        mEnvelope = env;
+        mPdu = pdu;
+
+        parseSms();
+    }
+
+    /**
+     * Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0
+     */
+    private void parsePduFromEfRecord(byte[] pdu) {
+        ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+        DataInputStream dis = new DataInputStream(bais);
+        SmsEnvelope env = new SmsEnvelope();
+        CdmaSmsAddress addr = new CdmaSmsAddress();
+        CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress();
+
+        try {
+            env.messageType = dis.readByte();
+
+            while (dis.available() > 0) {
+                int parameterId = dis.readByte();
+                int parameterLen = dis.readUnsignedByte();
+                byte[] parameterData = new byte[parameterLen];
+
+                switch (parameterId) {
+                    case TELESERVICE_IDENTIFIER:
+                        /*
+                         * 16 bit parameter that identifies which upper layer
+                         * service access point is sending or should receive
+                         * this message
+                         */
+                        env.teleService = dis.readUnsignedShort();
+                        Rlog.i(LOG_TAG, "teleservice = " + env.teleService);
+                        break;
+                    case SERVICE_CATEGORY:
+                        /*
+                         * 16 bit parameter that identifies type of service as
+                         * in 3GPP2 C.S0015-0 Table 3.4.3.2-1
+                         */
+                        env.serviceCategory = dis.readUnsignedShort();
+                        break;
+                    case ORIGINATING_ADDRESS:
+                    case DESTINATION_ADDRESS:
+                        dis.read(parameterData, 0, parameterLen);
+                        BitwiseInputStream addrBis = new BitwiseInputStream(parameterData);
+                        addr.digitMode = addrBis.read(1);
+                        addr.numberMode = addrBis.read(1);
+                        int numberType = 0;
+                        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+                            numberType = addrBis.read(3);
+                            addr.ton = numberType;
+
+                            if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK)
+                                addr.numberPlan = addrBis.read(4);
+                        }
+
+                        addr.numberOfDigits = addrBis.read(8);
+
+                        byte[] data = new byte[addr.numberOfDigits];
+                        byte b = 0x00;
+
+                        if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
+                            /* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */
+                            for (int index = 0; index < addr.numberOfDigits; index++) {
+                                b = (byte) (0xF & addrBis.read(4));
+                                // convert the value if it is 4-bit DTMF to 8
+                                // bit
+                                data[index] = convertDtmfToAscii(b);
+                            }
+                        } else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+                            if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) {
+                                for (int index = 0; index < addr.numberOfDigits; index++) {
+                                    b = (byte) (0xFF & addrBis.read(8));
+                                    data[index] = b;
+                                }
+
+                            } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) {
+                                if (numberType == 2)
+                                    Rlog.e(LOG_TAG, "TODO: Originating Addr is email id");
+                                else
+                                    Rlog.e(LOG_TAG,
+                                          "TODO: Originating Addr is data network address");
+                            } else {
+                                Rlog.e(LOG_TAG, "Originating Addr is of incorrect type");
+                            }
+                        } else {
+                            Rlog.e(LOG_TAG, "Incorrect Digit mode");
+                        }
+                        addr.origBytes = data;
+                        Rlog.i(LOG_TAG, "Originating Addr=" + addr.toString());
+                        break;
+                    case ORIGINATING_SUB_ADDRESS:
+                    case DESTINATION_SUB_ADDRESS:
+                        dis.read(parameterData, 0, parameterLen);
+                        BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData);
+                        subAddr.type = subAddrBis.read(3);
+                        subAddr.odd = subAddrBis.readByteArray(1)[0];
+                        int subAddrLen = subAddrBis.read(8);
+                        byte[] subdata = new byte[subAddrLen];
+                        for (int index = 0; index < subAddrLen; index++) {
+                            b = (byte) (0xFF & subAddrBis.read(4));
+                            // convert the value if it is 4-bit DTMF to 8 bit
+                            subdata[index] = convertDtmfToAscii(b);
+                        }
+                        subAddr.origBytes = subdata;
+                        break;
+                    case BEARER_REPLY_OPTION:
+                        dis.read(parameterData, 0, parameterLen);
+                        BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData);
+                        env.bearerReply = replyOptBis.read(6);
+                        break;
+                    case CAUSE_CODES:
+                        dis.read(parameterData, 0, parameterLen);
+                        BitwiseInputStream ccBis = new BitwiseInputStream(parameterData);
+                        env.replySeqNo = ccBis.readByteArray(6)[0];
+                        env.errorClass = ccBis.readByteArray(2)[0];
+                        if (env.errorClass != 0x00)
+                            env.causeCode = ccBis.readByteArray(8)[0];
+                        break;
+                    case BEARER_DATA:
+                        dis.read(parameterData, 0, parameterLen);
+                        env.bearerData = parameterData;
+                        break;
+                    default:
+                        throw new Exception("unsupported parameterId (" + parameterId + ")");
+                }
+            }
+            bais.close();
+            dis.close();
+        } catch (Exception ex) {
+            Rlog.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex);
+        }
+
+        // link the filled objects to this SMS
+        mOriginatingAddress = addr;
+        env.origAddress = addr;
+        env.origSubaddress = subAddr;
+        mEnvelope = env;
+        mPdu = pdu;
+
+        parseSms();
+    }
+
+    /**
+     * Parses a SMS message from its BearerData stream. (mobile-terminated only)
+     */
+    public void parseSms() {
+        // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6
+        // It contains only an 8-bit number with the number of messages waiting
+        if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) {
+            mBearerData = new BearerData();
+            if (mEnvelope.bearerData != null) {
+                mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0];
+            }
+            if (VDBG) {
+                Rlog.d(LOG_TAG, "parseSms: get MWI " +
+                      Integer.toString(mBearerData.numberOfMessages));
+            }
+            return;
+        }
+        mBearerData = BearerData.decode(mEnvelope.bearerData);
+        if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+            Rlog.d(LOG_TAG, "MT raw BearerData = '" +
+                      HexDump.toHexString(mEnvelope.bearerData) + "'");
+            Rlog.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData);
+        }
+        mMessageRef = mBearerData.messageId;
+        if (mBearerData.userData != null) {
+            mUserData = mBearerData.userData.payload;
+            mUserDataHeader = mBearerData.userData.userDataHeader;
+            mMessageBody = mBearerData.userData.payloadStr;
+        }
+
+        if (mOriginatingAddress != null) {
+            mOriginatingAddress.address = new String(mOriginatingAddress.origBytes);
+            if (mOriginatingAddress.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) {
+                if (mOriginatingAddress.address.charAt(0) != '+') {
+                    mOriginatingAddress.address = "+" + mOriginatingAddress.address;
+                }
+            }
+            if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+                    + mOriginatingAddress.address);
+        }
+
+        if (mBearerData.msgCenterTimeStamp != null) {
+            mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true);
+        }
+
+        if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
+
+        // Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1)
+        if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) {
+            // The BearerData MsgStatus subparameter should only be
+            // included for DELIVERY_ACK messages.  If it occurred for
+            // other messages, it would be unclear what the status
+            // being reported refers to.  The MsgStatus subparameter
+            // is primarily useful to indicate error conditions -- a
+            // message without this subparameter is assumed to
+            // indicate successful delivery (status == 0).
+            if (! mBearerData.messageStatusSet) {
+                Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" +
+                        (mUserData == null ? "also missing" : "does have") +
+                        " userData).");
+                status = 0;
+            } else {
+                status = mBearerData.errorClass << 8;
+                status |= mBearerData.messageStatus;
+            }
+        } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER) {
+            throw new RuntimeException("Unsupported message type: " + mBearerData.messageType);
+        }
+
+        if (mMessageBody != null) {
+            if (VDBG) Rlog.v(LOG_TAG, "SMS message body: '" + mMessageBody + "'");
+            parseMessageBody();
+        } else if ((mUserData != null) && VDBG) {
+            Rlog.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(mUserData) + "'");
+        }
+    }
+
+    /**
+     * Parses a broadcast SMS, possibly containing a CMAS alert.
+     */
+    public SmsCbMessage parseBroadcastSms() {
+        BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
+        if (bData == null) {
+            Rlog.w(LOG_TAG, "BearerData.decode() returned null");
+            return null;
+        }
+
+        if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+            Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
+        }
+
+        String plmn = TelephonyManager.getDefault().getNetworkOperator();
+        SmsCbLocation location = new SmsCbLocation(plmn);
+
+        return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
+                SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location,
+                mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr,
+                bData.priority, null, bData.cmasWarningInfo);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SmsConstants.MessageClass getMessageClass() {
+        if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) {
+            return SmsConstants.MessageClass.CLASS_0;
+        } else {
+            return SmsConstants.MessageClass.UNKNOWN;
+        }
+    }
+
+    /**
+     * Calculate the next message id, starting at 1 and iteratively
+     * incrementing within the range 1..65535 remembering the state
+     * via a persistent system property.  (See C.S0015-B, v2.0,
+     * 4.3.1.5) Since this routine is expected to be accessed via via
+     * binder-call, and hence should be thread-safe, it has been
+     * synchronized.
+     */
+    public synchronized static int getNextMessageId() {
+        // Testing and dialog with partners has indicated that
+        // msgId==0 is (sometimes?) treated specially by lower levels.
+        // Specifically, the ID is not preserved for delivery ACKs.
+        // Hence, avoid 0 -- constraining the range to 1..65535.
+        int msgId = SystemProperties.getInt(TelephonyProperties.PROPERTY_CDMA_MSG_ID, 1);
+        String nextMsgId = Integer.toString((msgId % 0xFFFF) + 1);
+        try{
+            SystemProperties.set(TelephonyProperties.PROPERTY_CDMA_MSG_ID, nextMsgId);
+            if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+                Rlog.d(LOG_TAG, "next " + TelephonyProperties.PROPERTY_CDMA_MSG_ID + " = " + nextMsgId);
+                Rlog.d(LOG_TAG, "readback gets " +
+                        SystemProperties.get(TelephonyProperties.PROPERTY_CDMA_MSG_ID));
+            }
+        } catch(RuntimeException ex) {
+            Rlog.e(LOG_TAG, "set nextMessage ID failed: " + ex);
+        }
+        return msgId;
+    }
+
+    /**
+     * Creates BearerData and Envelope from parameters for a Submit SMS.
+     * @return byte stream for SubmitPdu.
+     */
+    private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+            UserData userData) {
+
+        /**
+         * TODO(cleanup): give this function a more meaningful name.
+         */
+
+        /**
+         * TODO(cleanup): Make returning null from the getSubmitPdu
+         * variations meaningful -- clean up the error feedback
+         * mechanism, and avoid null pointer exceptions.
+         */
+
+        /**
+         * North America Plus Code :
+         * Convert + code to 011 and dial out for international SMS
+         */
+        CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr));
+        if (destAddr == null) return null;
+
+        BearerData bearerData = new BearerData();
+        bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
+
+        bearerData.messageId = getNextMessageId();
+
+        bearerData.deliveryAckReq = statusReportRequested;
+        bearerData.userAckReq = false;
+        bearerData.readAckReq = false;
+        bearerData.reportReq = false;
+
+        bearerData.userData = userData;
+
+        byte[] encodedBearerData = BearerData.encode(bearerData);
+        if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+            Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
+            Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
+        }
+        if (encodedBearerData == null) return null;
+
+        int teleservice = bearerData.hasUserDataHeader ?
+                SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;
+
+        SmsEnvelope envelope = new SmsEnvelope();
+        envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+        envelope.teleService = teleservice;
+        envelope.destAddress = destAddr;
+        envelope.bearerReply = RETURN_ACK;
+        envelope.bearerData = encodedBearerData;
+
+        /**
+         * TODO(cleanup): envelope looks to be a pointless class, get
+         * rid of it.  Also -- most of the envelope fields set here
+         * are ignored, why?
+         */
+
+        try {
+            /**
+             * TODO(cleanup): reference a spec and get rid of the ugly comments
+             */
+            ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+            DataOutputStream dos = new DataOutputStream(baos);
+            dos.writeInt(envelope.teleService);
+            dos.writeInt(0); //servicePresent
+            dos.writeInt(0); //serviceCategory
+            dos.write(destAddr.digitMode);
+            dos.write(destAddr.numberMode);
+            dos.write(destAddr.ton); // number_type
+            dos.write(destAddr.numberPlan);
+            dos.write(destAddr.numberOfDigits);
+            dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
+            // Subaddress is not supported.
+            dos.write(0); //subaddressType
+            dos.write(0); //subaddr_odd
+            dos.write(0); //subaddr_nbr_of_digits
+            dos.write(encodedBearerData.length);
+            dos.write(encodedBearerData, 0, encodedBearerData.length);
+            dos.close();
+
+            SubmitPdu pdu = new SubmitPdu();
+            pdu.encodedMessage = baos.toByteArray();
+            pdu.encodedScAddress = null;
+            return pdu;
+        } catch(IOException ex) {
+            Rlog.e(LOG_TAG, "creating SubmitPdu failed: " + ex);
+        }
+        return null;
+    }
+
+    /**
+     * Creates byte array (pseudo pdu) from SMS object.
+     * Note: Do not call this method more than once per object!
+     * @hide
+     */
+    public void createPdu() {
+        SmsEnvelope env = mEnvelope;
+        CdmaSmsAddress addr = env.origAddress;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+        DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
+
+        try {
+            dos.writeInt(env.messageType);
+            dos.writeInt(env.teleService);
+            dos.writeInt(env.serviceCategory);
+
+            dos.writeByte(addr.digitMode);
+            dos.writeByte(addr.numberMode);
+            dos.writeByte(addr.ton);
+            dos.writeByte(addr.numberPlan);
+            dos.writeByte(addr.numberOfDigits);
+            dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
+
+            dos.writeInt(env.bearerReply);
+            // CauseCode values:
+            dos.writeByte(env.replySeqNo);
+            dos.writeByte(env.errorClass);
+            dos.writeByte(env.causeCode);
+            //encoded BearerData:
+            dos.writeInt(env.bearerData.length);
+            dos.write(env.bearerData, 0, env.bearerData.length);
+            dos.close();
+
+            /**
+             * TODO(cleanup) -- The mPdu field is managed in
+             * a fragile manner, and it would be much nicer if
+             * accessing the serialized representation used a less
+             * fragile mechanism.  Maybe the getPdu method could
+             * generate a representation if there was not yet one?
+             */
+
+            mPdu = baos.toByteArray();
+        } catch (IOException ex) {
+            Rlog.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex);
+        }
+    }
+
+    /**
+     * Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character
+     * @hide
+     */
+    public static byte convertDtmfToAscii(byte dtmfDigit) {
+        byte asciiDigit;
+
+        switch (dtmfDigit) {
+        case  0: asciiDigit = 68; break; // 'D'
+        case  1: asciiDigit = 49; break; // '1'
+        case  2: asciiDigit = 50; break; // '2'
+        case  3: asciiDigit = 51; break; // '3'
+        case  4: asciiDigit = 52; break; // '4'
+        case  5: asciiDigit = 53; break; // '5'
+        case  6: asciiDigit = 54; break; // '6'
+        case  7: asciiDigit = 55; break; // '7'
+        case  8: asciiDigit = 56; break; // '8'
+        case  9: asciiDigit = 57; break; // '9'
+        case 10: asciiDigit = 48; break; // '0'
+        case 11: asciiDigit = 42; break; // '*'
+        case 12: asciiDigit = 35; break; // '#'
+        case 13: asciiDigit = 65; break; // 'A'
+        case 14: asciiDigit = 66; break; // 'B'
+        case 15: asciiDigit = 67; break; // 'C'
+        default:
+            asciiDigit = 32; // Invalid DTMF code
+            break;
+        }
+
+        return asciiDigit;
+    }
+
+    /** This function  shall be called to get the number of voicemails.
+     * @hide
+     */
+    public int getNumOfVoicemails() {
+        return mBearerData.numberOfMessages;
+    }
+
+    /**
+     * Returns a byte array that can be use to uniquely identify a received SMS message.
+     * C.S0015-B  4.3.1.6 Unique Message Identification.
+     *
+     * @return byte array uniquely identifying the message.
+     * @hide
+     */
+    public byte[] getIncomingSmsFingerprint() {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+        output.write(mEnvelope.serviceCategory);
+        output.write(mEnvelope.teleService);
+        output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length);
+        output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length);
+        output.write(mEnvelope.origSubaddress.origBytes, 0,
+                mEnvelope.origSubaddress.origBytes.length);
+
+        return output.toByteArray();
+    }
+
+    /**
+     * Returns the list of service category program data, if present.
+     * @return a list of CdmaSmsCbProgramData objects, or null if not present
+     * @hide
+     */
+    public ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() {
+        return mBearerData.serviceCategoryProgramData;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/UserData.java b/telephony/java/com/android/internal/telephony/cdma/UserData.java
new file mode 100644
index 0000000..599c2b3
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/UserData.java
@@ -0,0 +1,165 @@
+/*
+ * 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.telephony.cdma.sms;
+
+import android.util.SparseIntArray;
+
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.util.HexDump;
+
+public class UserData {
+
+    /**
+     * User data encoding types.
+     * (See 3GPP2 C.R1001-F, v1.0, table 9.1-1)
+     */
+    public static final int ENCODING_OCTET                      = 0x00;
+    public static final int ENCODING_IS91_EXTENDED_PROTOCOL     = 0x01;
+    public static final int ENCODING_7BIT_ASCII                 = 0x02;
+    public static final int ENCODING_IA5                        = 0x03;
+    public static final int ENCODING_UNICODE_16                 = 0x04;
+    public static final int ENCODING_SHIFT_JIS                  = 0x05;
+    public static final int ENCODING_KOREAN                     = 0x06;
+    public static final int ENCODING_LATIN_HEBREW               = 0x07;
+    public static final int ENCODING_LATIN                      = 0x08;
+    public static final int ENCODING_GSM_7BIT_ALPHABET          = 0x09;
+    public static final int ENCODING_GSM_DCS                    = 0x0A;
+
+    /**
+     * IS-91 message types.
+     * (See TIA/EIS/IS-91-A-ENGL 1999, table 3.7.1.1-3)
+     */
+    public static final int IS91_MSG_TYPE_VOICEMAIL_STATUS   = 0x82;
+    public static final int IS91_MSG_TYPE_SHORT_MESSAGE_FULL = 0x83;
+    public static final int IS91_MSG_TYPE_CLI                = 0x84;
+    public static final int IS91_MSG_TYPE_SHORT_MESSAGE      = 0x85;
+
+    /**
+     * US ASCII character mapping table.
+     *
+     * This table contains only the printable ASCII characters, with a
+     * 0x20 offset, meaning that the ASCII SPACE character is at index
+     * 0, with the resulting code of 0x20.
+     *
+     * Note this mapping is also equivalent to that used by both the
+     * IA5 and the IS-91 encodings.  For the former this is defined
+     * using CCITT Rec. T.50 Tables 1 and 3.  For the latter IS 637 B,
+     * Table 4.3.1.4.1-1 -- and note the encoding uses only 6 bits,
+     * and hence only maps entries up to the '_' character.
+     *
+     */
+    public static final char[] ASCII_MAP = {
+        ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
+        '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+        'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
+        '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+        'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'};
+
+    /**
+     * Character to use when forced to encode otherwise unencodable
+     * characters, meaning those not in the respective ASCII or GSM
+     * 7-bit encoding tables.  Current choice is SPACE, which is 0x20
+     * in both the GSM-7bit and ASCII-7bit encodings.
+     */
+    static final byte UNENCODABLE_7_BIT_CHAR = 0x20;
+
+    /**
+     * Only elements between these indices in the ASCII table are printable.
+     */
+    public static final int PRINTABLE_ASCII_MIN_INDEX = 0x20;
+    public static final int ASCII_NL_INDEX = 0x0A;
+    public static final int ASCII_CR_INDEX = 0x0D;
+    public static final SparseIntArray charToAscii = new SparseIntArray();
+    static {
+        for (int i = 0; i < ASCII_MAP.length; i++) {
+            charToAscii.put(ASCII_MAP[i], PRINTABLE_ASCII_MIN_INDEX + i);
+        }
+        charToAscii.put('\n', ASCII_NL_INDEX);
+        charToAscii.put('\r', ASCII_CR_INDEX);
+    }
+
+    /*
+     * TODO(cleanup): Move this very generic functionality somewhere
+     * more general.
+     */
+    /**
+     * Given a string generate a corresponding ASCII-encoded byte
+     * array, but limited to printable characters.  If the input
+     * contains unprintable characters, return null.
+     */
+    public static byte[] stringToAscii(String str) {
+        int len = str.length();
+        byte[] result = new byte[len];
+        for (int i = 0; i < len; i++) {
+            int charCode = charToAscii.get(str.charAt(i), -1);
+            if (charCode == -1) return null;
+            result[i] = (byte)charCode;
+        }
+        return result;
+    }
+
+    /**
+     * Mapping for ASCII values less than 32 are flow control signals
+     * and not used here.
+     */
+    public static final int ASCII_MAP_BASE_INDEX = 0x20;
+    public static final int ASCII_MAP_MAX_INDEX = ASCII_MAP_BASE_INDEX + ASCII_MAP.length - 1;
+
+    /**
+     * Contains the data header of the user data
+     */
+    public SmsHeader userDataHeader;
+
+    /**
+     * Contains the data encoding type for the SMS message
+     */
+    public int msgEncoding;
+    public boolean msgEncodingSet = false;
+
+    public int msgType;
+
+    /**
+     * Number of invalid bits in the last byte of data.
+     */
+    public int paddingBits;
+
+    public int numFields;
+
+    /**
+     * Contains the user data of a SMS message
+     * (See 3GPP2 C.S0015-B, v2, 4.5.2)
+     */
+    public byte[] payload;
+    public String payloadStr;
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("UserData ");
+        builder.append("{ msgEncoding=" + (msgEncodingSet ? msgEncoding : "unset"));
+        builder.append(", msgType=" + msgType);
+        builder.append(", paddingBits=" + paddingBits);
+        builder.append(", numFields=" + numFields);
+        builder.append(", userDataHeader=" + userDataHeader);
+        builder.append(", payload='" + HexDump.toHexString(payload) + "'");
+        builder.append(", payloadStr='" + payloadStr + "'");
+        builder.append(" }");
+        return builder.toString();
+    }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/package.html b/telephony/java/com/android/internal/telephony/cdma/package.html
new file mode 100644
index 0000000..b2bc736
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides CDMA-specific features for text/data/PDU SMS messages
+@hide
+</BODY>
+</HTML>
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
new file mode 100644
index 0000000..2fbf7ed
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
@@ -0,0 +1,153 @@
+/*
+ * 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 com.android.internal.telephony.gsm;
+
+import android.telephony.PhoneNumberUtils;
+import java.text.ParseException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsAddress;
+
+public class GsmSmsAddress extends SmsAddress {
+
+    static final int OFFSET_ADDRESS_LENGTH = 0;
+
+    static final int OFFSET_TOA = 1;
+
+    static final int OFFSET_ADDRESS_VALUE = 2;
+
+    /**
+     * New GsmSmsAddress from TS 23.040 9.1.2.5 Address Field
+     *
+     * @param offset the offset of the Address-Length byte
+     * @param length the length in bytes rounded up, e.g. "2 +
+     *        (addressLength + 1) / 2"
+     * @throws ParseException
+     */
+
+    public GsmSmsAddress(byte[] data, int offset, int length) throws ParseException {
+        origBytes = new byte[length];
+        System.arraycopy(data, offset, origBytes, 0, length);
+
+        // addressLength is the count of semi-octets, not bytes
+        int addressLength = origBytes[OFFSET_ADDRESS_LENGTH] & 0xff;
+
+        int toa = origBytes[OFFSET_TOA] & 0xff;
+        ton = 0x7 & (toa >> 4);
+
+        // TOA must have its high bit set
+        if ((toa & 0x80) != 0x80) {
+            throw new ParseException("Invalid TOA - high bit must be set. toa = " + toa,
+                    offset + OFFSET_TOA);
+        }
+
+        if (isAlphanumeric()) {
+            // An alphanumeric address
+            int countSeptets = addressLength * 4 / 7;
+
+            address = GsmAlphabet.gsm7BitPackedToString(origBytes,
+                    OFFSET_ADDRESS_VALUE, countSeptets);
+        } else {
+            // TS 23.040 9.1.2.5 says
+            // that "the MS shall interpret reserved values as 'Unknown'
+            // but shall store them exactly as received"
+
+            byte lastByte = origBytes[length - 1];
+
+            if ((addressLength & 1) == 1) {
+                // Make sure the final unused BCD digit is 0xf
+                origBytes[length - 1] |= 0xf0;
+            }
+            address = PhoneNumberUtils.calledPartyBCDToString(origBytes,
+                    OFFSET_TOA, length - OFFSET_TOA);
+
+            // And restore origBytes
+            origBytes[length - 1] = lastByte;
+        }
+    }
+
+    @Override
+    public String getAddressString() {
+        return address;
+    }
+
+    /**
+     * Returns true if this is an alphanumeric address
+     */
+    @Override
+    public boolean isAlphanumeric() {
+        return ton == TON_ALPHANUMERIC;
+    }
+
+    @Override
+    public boolean isNetworkSpecific() {
+        return ton == TON_NETWORK;
+    }
+
+    /**
+     * Returns true of this is a valid CPHS voice message waiting indicator
+     * address
+     */
+    public boolean isCphsVoiceMessageIndicatorAddress() {
+        // CPHS-style MWI message
+        // See CPHS 4.7 B.4.2.1
+        //
+        // Basically:
+        //
+        // - Originating address should be 4 bytes long and alphanumeric
+        // - Decode will result with two chars:
+        // - Char 1
+        // 76543210
+        // ^ set/clear indicator (0 = clear)
+        // ^^^ type of indicator (000 = voice)
+        // ^^^^ must be equal to 0001
+        // - Char 2:
+        // 76543210
+        // ^ line number (0 = line 1)
+        // ^^^^^^^ set to 0
+        //
+        // Remember, since the alpha address is stored in 7-bit compact form,
+        // the "line number" is really the top bit of the first address value
+        // byte
+
+        return (origBytes[OFFSET_ADDRESS_LENGTH] & 0xff) == 4
+                && isAlphanumeric() && (origBytes[OFFSET_TOA] & 0x0f) == 0;
+    }
+
+    /**
+     * Returns true if this is a valid CPHS voice message waiting indicator
+     * address indicating a "set" of "indicator 1" of type "voice message
+     * waiting"
+     */
+    public boolean isCphsVoiceMessageSet() {
+        // 0x11 means "set" "voice message waiting" "indicator 1"
+        return isCphsVoiceMessageIndicatorAddress()
+                && (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x11;
+
+    }
+
+    /**
+     * Returns true if this is a valid CPHS voice message waiting indicator
+     * address indicating a "clear" of "indicator 1" of type "voice message
+     * waiting"
+     */
+    public boolean isCphsVoiceMessageClear() {
+        // 0x10 means "clear" "voice message waiting" "indicator 1"
+        return isCphsVoiceMessageIndicatorAddress()
+                && (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x10;
+
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
new file mode 100644
index 0000000..6bf22a0
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2012 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.telephony.gsm;
+
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.util.Pair;
+
+import com.android.internal.R;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsConstants;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
+ * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
+ */
+public class GsmSmsCbMessage {
+
+    /**
+     * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+     */
+    private static final String[] LANGUAGE_CODES_GROUP_0 = {
+            "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu",
+            "pl", null
+    };
+
+    /**
+     * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+     */
+    private static final String[] LANGUAGE_CODES_GROUP_2 = {
+            "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null,
+            null, null
+    };
+
+    private static final char CARRIAGE_RETURN = 0x0d;
+
+    private static final int PDU_BODY_PAGE_LENGTH = 82;
+
+    /** Utility class with only static methods. */
+    private GsmSmsCbMessage() { }
+
+    /**
+     * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
+     * so we have to show the pre-built messages to the user.
+     *
+     * @param context Device context
+     * @param category ETWS message category defined in SmsCbConstants
+     * @return ETWS text message in string. Return an empty string if no match.
+     */
+    private static String getEtwsPrimaryMessage(Context context, int category) {
+        final Resources r = context.getResources();
+        switch (category) {
+            case ETWS_WARNING_TYPE_EARTHQUAKE:
+                return r.getString(R.string.etws_primary_default_message_earthquake);
+            case ETWS_WARNING_TYPE_TSUNAMI:
+                return r.getString(R.string.etws_primary_default_message_tsunami);
+            case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
+                return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
+            case ETWS_WARNING_TYPE_TEST_MESSAGE:
+                return r.getString(R.string.etws_primary_default_message_test);
+            case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
+                return r.getString(R.string.etws_primary_default_message_others);
+            default:
+                return "";
+        }
+    }
+
+    /**
+     * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
+     *
+     * @param pdus PDU bytes
+     */
+    public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
+                                                  SmsCbLocation location, byte[][] pdus)
+            throws IllegalArgumentException {
+        if (header.isEtwsPrimaryNotification()) {
+            // ETSI TS 23.041 ETWS Primary Notification message
+            // ETWS primary message only contains 4 fields including serial number,
+            // message identifier, warning type, and warning security information.
+            // There is no field for the content/text so we get the text from the resources.
+            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
+                    header.getSerialNumber(), location, header.getServiceCategory(), null,
+                    getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
+                    SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
+                    header.getCmasInfo());
+        } else {
+            String language = null;
+            StringBuilder sb = new StringBuilder();
+            for (byte[] pdu : pdus) {
+                Pair<String, String> p = parseBody(header, pdu);
+                language = p.first;
+                sb.append(p.second);
+            }
+            int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
+                    : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
+
+            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
+                    header.getGeographicalScope(), header.getSerialNumber(), location,
+                    header.getServiceCategory(), language, sb.toString(), priority,
+                    header.getEtwsInfo(), header.getCmasInfo());
+        }
+    }
+
+    /**
+     * Parse and unpack the body text according to the encoding in the DCS.
+     * After completing successfully this method will have assigned the body
+     * text into mBody, and optionally the language code into mLanguage
+     *
+     * @param header the message header to use
+     * @param pdu the PDU to decode
+     * @return a Pair of Strings containing the language and body of the message
+     */
+    private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) {
+        int encoding;
+        String language = null;
+        boolean hasLanguageIndicator = false;
+        int dataCodingScheme = header.getDataCodingScheme();
+
+        // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
+        // section 5.
+        switch ((dataCodingScheme & 0xf0) >> 4) {
+            case 0x00:
+                encoding = SmsConstants.ENCODING_7BIT;
+                language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
+                break;
+
+            case 0x01:
+                hasLanguageIndicator = true;
+                if ((dataCodingScheme & 0x0f) == 0x01) {
+                    encoding = SmsConstants.ENCODING_16BIT;
+                } else {
+                    encoding = SmsConstants.ENCODING_7BIT;
+                }
+                break;
+
+            case 0x02:
+                encoding = SmsConstants.ENCODING_7BIT;
+                language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
+                break;
+
+            case 0x03:
+                encoding = SmsConstants.ENCODING_7BIT;
+                break;
+
+            case 0x04:
+            case 0x05:
+                switch ((dataCodingScheme & 0x0c) >> 2) {
+                    case 0x01:
+                        encoding = SmsConstants.ENCODING_8BIT;
+                        break;
+
+                    case 0x02:
+                        encoding = SmsConstants.ENCODING_16BIT;
+                        break;
+
+                    case 0x00:
+                    default:
+                        encoding = SmsConstants.ENCODING_7BIT;
+                        break;
+                }
+                break;
+
+            case 0x06:
+            case 0x07:
+                // Compression not supported
+            case 0x09:
+                // UDH structure not supported
+            case 0x0e:
+                // Defined by the WAP forum not supported
+                throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
+                        + dataCodingScheme);
+
+            case 0x0f:
+                if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
+                    encoding = SmsConstants.ENCODING_8BIT;
+                } else {
+                    encoding = SmsConstants.ENCODING_7BIT;
+                }
+                break;
+
+            default:
+                // Reserved values are to be treated as 7-bit
+                encoding = SmsConstants.ENCODING_7BIT;
+                break;
+        }
+
+        if (header.isUmtsFormat()) {
+            // Payload may contain multiple pages
+            int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
+
+            if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
+                    * nrPages) {
+                throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
+                        + nrPages + " pages");
+            }
+
+            StringBuilder sb = new StringBuilder();
+
+            for (int i = 0; i < nrPages; i++) {
+                // Each page is 82 bytes followed by a length octet indicating
+                // the number of useful octets within those 82
+                int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
+                int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
+
+                if (length > PDU_BODY_PAGE_LENGTH) {
+                    throw new IllegalArgumentException("Page length " + length
+                            + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
+                }
+
+                Pair<String, String> p = unpackBody(pdu, encoding, offset, length,
+                        hasLanguageIndicator, language);
+                language = p.first;
+                sb.append(p.second);
+            }
+            return new Pair<String, String>(language, sb.toString());
+        } else {
+            // Payload is one single page
+            int offset = SmsCbHeader.PDU_HEADER_LENGTH;
+            int length = pdu.length - offset;
+
+            return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language);
+        }
+    }
+
+    /**
+     * Unpack body text from the pdu using the given encoding, position and
+     * length within the pdu
+     *
+     * @param pdu The pdu
+     * @param encoding The encoding, as derived from the DCS
+     * @param offset Position of the first byte to unpack
+     * @param length Number of bytes to unpack
+     * @param hasLanguageIndicator true if the body text is preceded by a
+     *            language indicator. If so, this method will as a side-effect
+     *            assign the extracted language code into mLanguage
+     * @param language the language to return if hasLanguageIndicator is false
+     * @return a Pair of Strings containing the language and body of the message
+     */
+    private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length,
+            boolean hasLanguageIndicator, String language) {
+        String body = null;
+
+        switch (encoding) {
+            case SmsConstants.ENCODING_7BIT:
+                body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
+
+                if (hasLanguageIndicator && body != null && body.length() > 2) {
+                    // Language is two GSM characters followed by a CR.
+                    // The actual body text is offset by 3 characters.
+                    language = body.substring(0, 2);
+                    body = body.substring(3);
+                }
+                break;
+
+            case SmsConstants.ENCODING_16BIT:
+                if (hasLanguageIndicator && pdu.length >= offset + 2) {
+                    // Language is two GSM characters.
+                    // The actual body text is offset by 2 bytes.
+                    language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
+                    offset += 2;
+                    length -= 2;
+                }
+
+                try {
+                    body = new String(pdu, offset, (length & 0xfffe), "utf-16");
+                } catch (UnsupportedEncodingException e) {
+                    // Apparently it wasn't valid UTF-16.
+                    throw new IllegalArgumentException("Error decoding UTF-16 message", e);
+                }
+                break;
+
+            default:
+                break;
+        }
+
+        if (body != null) {
+            // Remove trailing carriage return
+            for (int i = body.length() - 1; i >= 0; i--) {
+                if (body.charAt(i) != CARRIAGE_RETURN) {
+                    body = body.substring(0, i + 1);
+                    break;
+                }
+            }
+        } else {
+            body = "";
+        }
+
+        return new Pair<String, String>(language, body);
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
new file mode 100644
index 0000000..f4f4036
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2009 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.telephony.gsm;
+
+/**
+ * SmsBroadcastConfigInfo defines one configuration of Cell Broadcast
+ * Message (CBM) to be received by the ME
+ *
+ * fromServiceId - toServiceId defines a range of CBM message identifiers
+ * whose value is 0x0000 - 0xFFFF as defined in TS 23.041 9.4.1.2.2 for GMS
+ * and 9.4.4.2.2 for UMTS. All other values can be treated as empty
+ * CBM message ID.
+ *
+ * fromCodeScheme - toCodeScheme defines a range of CBM data coding schemes
+ * whose value is 0x00 - 0xFF as defined in TS 23.041 9.4.1.2.3 for GMS
+ * and 9.4.4.2.3 for UMTS.
+ * All other values can be treated as empty CBM data coding scheme.
+ *
+ * selected false means message types specified in {@code <fromServiceId, toServiceId>}
+ * and {@code <fromCodeScheme, toCodeScheme>} are not accepted, while true means accepted.
+ *
+ */
+public final class SmsBroadcastConfigInfo {
+    private int mFromServiceId;
+    private int mToServiceId;
+    private int mFromCodeScheme;
+    private int mToCodeScheme;
+    private boolean mSelected;
+
+    /**
+     * Initialize the object from rssi and cid.
+     */
+    public SmsBroadcastConfigInfo(int fromId, int toId, int fromScheme,
+            int toScheme, boolean selected) {
+        mFromServiceId = fromId;
+        mToServiceId = toId;
+        mFromCodeScheme = fromScheme;
+        mToCodeScheme = toScheme;
+        mSelected = selected;
+    }
+
+    /**
+     * @param fromServiceId the fromServiceId to set
+     */
+    public void setFromServiceId(int fromServiceId) {
+        mFromServiceId = fromServiceId;
+    }
+
+    /**
+     * @return the fromServiceId
+     */
+    public int getFromServiceId() {
+        return mFromServiceId;
+    }
+
+    /**
+     * @param toServiceId the toServiceId to set
+     */
+    public void setToServiceId(int toServiceId) {
+        mToServiceId = toServiceId;
+    }
+
+    /**
+     * @return the toServiceId
+     */
+    public int getToServiceId() {
+        return mToServiceId;
+    }
+
+    /**
+     * @param fromCodeScheme the fromCodeScheme to set
+     */
+    public void setFromCodeScheme(int fromCodeScheme) {
+        mFromCodeScheme = fromCodeScheme;
+    }
+
+    /**
+     * @return the fromCodeScheme
+     */
+    public int getFromCodeScheme() {
+        return mFromCodeScheme;
+    }
+
+    /**
+     * @param toCodeScheme the toCodeScheme to set
+     */
+    public void setToCodeScheme(int toCodeScheme) {
+        mToCodeScheme = toCodeScheme;
+    }
+
+    /**
+     * @return the toCodeScheme
+     */
+    public int getToCodeScheme() {
+        return mToCodeScheme;
+    }
+
+    /**
+     * @param selected the selected to set
+     */
+    public void setSelected(boolean selected) {
+        mSelected = selected;
+    }
+
+    /**
+     * @return the selected
+     */
+    public boolean isSelected() {
+        return mSelected;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsBroadcastConfigInfo: Id [" +
+                mFromServiceId + ',' + mToServiceId + "] Code [" +
+                mFromCodeScheme + ',' + mToCodeScheme + "] " +
+            (mSelected ? "ENABLED" : "DISABLED");
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
new file mode 100644
index 0000000..bce5680
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2012 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.telephony.gsm;
+
+/**
+ * Constants used in SMS Cell Broadcast messages (see 3GPP TS 23.041). This class is used by the
+ * boot-time broadcast channel enable and database upgrade code in CellBroadcastReceiver, so it
+ * is public, but should be avoided in favor of the radio technology independent constants in
+ * {@link android.telephony.SmsCbMessage}, {@link android.telephony.SmsCbEtwsInfo}, and
+ * {@link android.telephony.SmsCbCmasInfo} classes.
+ *
+ * {@hide}
+ */
+public class SmsCbConstants {
+
+    /** Private constructor for utility class. */
+    private SmsCbConstants() { }
+
+    /** Channel 50 required by Brazil. ID 0~999 is allocated by GSMA */
+    public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_50
+            = 0x0032;
+
+    /** Channel 911 required by Taiwan NCC. ID 0~999 is allocated by GSMA */
+    public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_911
+            = 0x038F; // 911
+
+    /** Channel 919 required by Taiwan NCC and Israel. ID 0~999 is allocated by GSMA */
+    public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_919
+            = 0x0397; // 919
+
+    /** Channel 928 required by Israel. ID 0~999 is allocated by GSMA */
+    public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_928
+            = 0x03A0; // 928
+
+    /** Start of PWS Message Identifier range (includes ETWS and CMAS). */
+    public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER
+            = 0x1100; // 4352
+
+    /** Bitmask for messages of ETWS type (including future extensions). */
+    public static final int MESSAGE_ID_ETWS_TYPE_MASK
+            = 0xFFF8;
+
+    /** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */
+    public static final int MESSAGE_ID_ETWS_TYPE
+            = 0x1100; // 4352
+
+    /** ETWS Message Identifier for earthquake warning message. */
+    public static final int MESSAGE_ID_ETWS_EARTHQUAKE_WARNING
+            = 0x1100; // 4352
+
+    /** ETWS Message Identifier for tsunami warning message. */
+    public static final int MESSAGE_ID_ETWS_TSUNAMI_WARNING
+            = 0x1101; // 4353
+
+    /** ETWS Message Identifier for earthquake and tsunami combined warning message. */
+    public static final int MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING
+            = 0x1102; // 4354
+
+    /** ETWS Message Identifier for test message. */
+    public static final int MESSAGE_ID_ETWS_TEST_MESSAGE
+            = 0x1103; // 4355
+
+    /** ETWS Message Identifier for messages related to other emergency types. */
+    public static final int MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE
+            = 0x1104; // 4356
+
+    /** Start of CMAS Message Identifier range. */
+    public static final int MESSAGE_ID_CMAS_FIRST_IDENTIFIER
+            = 0x1112; // 4370
+
+    /** CMAS Message Identifier for Presidential Level alerts. */
+    public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL
+            = 0x1112; // 4370
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED
+            = 0x1113; // 4371
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY
+            = 0x1114; // 4372
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED
+            = 0x1115; // 4373
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY
+            = 0x1116; // 4374
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED
+            = 0x1117; // 4375
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY
+            = 0x1118; // 4376
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED
+            = 0x1119; // 4377
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY
+            = 0x111A; // 4378
+
+    /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert). */
+    public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY
+            = 0x111B; // 4379
+
+    /** CMAS Message Identifier for the Required Monthly Test. */
+    public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST
+            = 0x111C; // 4380
+
+    /** CMAS Message Identifier for CMAS Exercise. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE
+            = 0x111D; // 4381
+
+    /** CMAS Message Identifier for operator defined use. */
+    public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE
+            = 0x111E; // 4382
+
+    /** CMAS Message Identifier for Presidential Level alerts for additional languages
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE
+            = 0x111F; // 4383
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE
+            = 0x1120; // 4384
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE
+            = 0x1121; // 4385
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE
+            = 0x1122; // 4386
+
+    /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE
+            = 0x1123; // 4387
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE
+            = 0x1124; // 4388
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely
+     *  for additional languages.*/
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE
+            = 0x1125; // 4389
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE
+            = 0x1126; // 4390
+
+    /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely
+     *  for additional languages.*/
+    public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE
+            = 0x1127; // 4391
+
+    /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert)
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE
+            = 0x1128; // 4392
+
+    /** CMAS Message Identifier for the Required Monthly Test
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE
+            = 0x1129; // 4393
+
+    /** CMAS Message Identifier for CMAS Exercise
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE
+            = 0x112A; // 4394
+
+    /** CMAS Message Identifier for operator defined use
+     *  for additional languages. */
+    public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE
+            = 0x112B; // 4395
+
+    /** End of CMAS Message Identifier range (including future extensions). */
+    public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER
+            = 0x112F; // 4399
+
+    /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
+    public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER
+            = 0x18FF; // 6399
+
+    /** ETWS serial number flag to activate the popup display. */
+    public static final int SERIAL_NUMBER_ETWS_ACTIVATE_POPUP
+            = 0x1000; // 4096
+
+    /** ETWS serial number flag to activate the emergency user alert. */
+    public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT
+            = 0x2000; // 8192
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
new file mode 100644
index 0000000..d267ad2
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2010 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.telephony.gsm;
+
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbEtwsInfo;
+
+import java.util.Arrays;
+
+/**
+ * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by
+ * CellBroadcastReceiver test cases, but should not be used by applications.
+ *
+ * All relevant header information is now sent as a Parcelable
+ * {@link android.telephony.SmsCbMessage} object in the "message" extra of the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or
+ * {@link android.provider.Telephony.Sms.Intents#SMS_EMERGENCY_CB_RECEIVED_ACTION} intent.
+ * The raw PDU is no longer sent to SMS CB applications.
+ */
+public class SmsCbHeader {
+
+    /**
+     * Length of SMS-CB header
+     */
+    static final int PDU_HEADER_LENGTH = 6;
+
+    /**
+     * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
+     */
+    static final int FORMAT_GSM = 1;
+
+    /**
+     * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
+     */
+    static final int FORMAT_UMTS = 2;
+
+    /**
+     * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
+     */
+    static final int FORMAT_ETWS_PRIMARY = 3;
+
+    /**
+     * Message type value as defined in 3gpp TS 25.324, section 11.1.
+     */
+    private static final int MESSAGE_TYPE_CBS_MESSAGE = 1;
+
+    /**
+     * Length of GSM pdus
+     */
+    private static final int PDU_LENGTH_GSM = 88;
+
+    /**
+     * Maximum length of ETWS primary message GSM pdus
+     */
+    private static final int PDU_LENGTH_ETWS = 56;
+
+    private final int mGeographicalScope;
+
+    /** The serial number combines geographical scope, message code, and update number. */
+    private final int mSerialNumber;
+
+    /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
+    private final int mMessageIdentifier;
+
+    private final int mDataCodingScheme;
+
+    private final int mPageIndex;
+
+    private final int mNrOfPages;
+
+    private final int mFormat;
+
+    /** ETWS warning notification info. */
+    private final SmsCbEtwsInfo mEtwsInfo;
+
+    /** CMAS warning notification info. */
+    private final SmsCbCmasInfo mCmasInfo;
+
+    public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
+        if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
+            throw new IllegalArgumentException("Illegal PDU");
+        }
+
+        if (pdu.length <= PDU_LENGTH_GSM) {
+            // can be ETWS or GSM format.
+            // Per TS23.041 9.4.1.2 and 9.4.1.3.2, GSM and ETWS format both
+            // contain serial number which contains GS, Message Code, and Update Number
+            // per 9.4.1.2.1, and message identifier in same octets
+            mGeographicalScope = (pdu[0] & 0xc0) >>> 6;
+            mSerialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
+            mMessageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
+            if (isEtwsMessage() && pdu.length <= PDU_LENGTH_ETWS) {
+                mFormat = FORMAT_ETWS_PRIMARY;
+                mDataCodingScheme = -1;
+                mPageIndex = -1;
+                mNrOfPages = -1;
+                boolean emergencyUserAlert = (pdu[4] & 0x1) != 0;
+                boolean activatePopup = (pdu[5] & 0x80) != 0;
+                int warningType = (pdu[4] & 0xfe) >>> 1;
+                byte[] warningSecurityInfo;
+                // copy the Warning-Security-Information, if present
+                if (pdu.length > PDU_HEADER_LENGTH) {
+                    warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length);
+                } else {
+                    warningSecurityInfo = null;
+                }
+                mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
+                        true, warningSecurityInfo);
+                mCmasInfo = null;
+                return;     // skip the ETWS/CMAS initialization code for regular notifications
+            } else {
+                // GSM pdus are no more than 88 bytes
+                mFormat = FORMAT_GSM;
+                mDataCodingScheme = pdu[4] & 0xff;
+
+                // Check for invalid page parameter
+                int pageIndex = (pdu[5] & 0xf0) >>> 4;
+                int nrOfPages = pdu[5] & 0x0f;
+
+                if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
+                    pageIndex = 1;
+                    nrOfPages = 1;
+                }
+
+                mPageIndex = pageIndex;
+                mNrOfPages = nrOfPages;
+            }
+        } else {
+            // UMTS pdus are always at least 90 bytes since the payload includes
+            // a number-of-pages octet and also one length octet per page
+            mFormat = FORMAT_UMTS;
+
+            int messageType = pdu[0];
+
+            if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
+                throw new IllegalArgumentException("Unsupported message type " + messageType);
+            }
+
+            mMessageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
+            mGeographicalScope = (pdu[3] & 0xc0) >>> 6;
+            mSerialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
+            mDataCodingScheme = pdu[5] & 0xff;
+
+            // We will always consider a UMTS message as having one single page
+            // since there's only one instance of the header, even though the
+            // actual payload may contain several pages.
+            mPageIndex = 1;
+            mNrOfPages = 1;
+        }
+
+        if (isEtwsMessage()) {
+            boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
+            boolean activatePopup = isEtwsPopupAlert();
+            int warningType = getEtwsWarningType();
+            mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
+                    false, null);
+            mCmasInfo = null;
+        } else if (isCmasMessage()) {
+            int messageClass = getCmasMessageClass();
+            int severity = getCmasSeverity();
+            int urgency = getCmasUrgency();
+            int certainty = getCmasCertainty();
+            mEtwsInfo = null;
+            mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
+                    SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty);
+        } else {
+            mEtwsInfo = null;
+            mCmasInfo = null;
+        }
+    }
+
+    int getGeographicalScope() {
+        return mGeographicalScope;
+    }
+
+    int getSerialNumber() {
+        return mSerialNumber;
+    }
+
+    int getServiceCategory() {
+        return mMessageIdentifier;
+    }
+
+    int getDataCodingScheme() {
+        return mDataCodingScheme;
+    }
+
+    int getPageIndex() {
+        return mPageIndex;
+    }
+
+    int getNumberOfPages() {
+        return mNrOfPages;
+    }
+
+    SmsCbEtwsInfo getEtwsInfo() {
+        return mEtwsInfo;
+    }
+
+    SmsCbCmasInfo getCmasInfo() {
+        return mCmasInfo;
+    }
+
+    /**
+     * Return whether this broadcast is an emergency (PWS) message type.
+     * @return true if this message is emergency type; false otherwise
+     */
+    boolean isEmergencyMessage() {
+        return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
+                && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
+    }
+
+    /**
+     * Return whether this broadcast is an ETWS emergency message type.
+     * @return true if this message is ETWS emergency type; false otherwise
+     */
+    private boolean isEtwsMessage() {
+        return (mMessageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
+                == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
+    }
+
+    /**
+     * Return whether this broadcast is an ETWS primary notification.
+     * @return true if this message is an ETWS primary notification; false otherwise
+     */
+    boolean isEtwsPrimaryNotification() {
+        return mFormat == FORMAT_ETWS_PRIMARY;
+    }
+
+    /**
+     * Return whether this broadcast is in UMTS format.
+     * @return true if this message is in UMTS format; false otherwise
+     */
+    boolean isUmtsFormat() {
+        return mFormat == FORMAT_UMTS;
+    }
+
+    /**
+     * Return whether this message is a CMAS emergency message type.
+     * @return true if this message is CMAS emergency type; false otherwise
+     */
+    private boolean isCmasMessage() {
+        return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
+                && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
+    }
+
+    /**
+     * Return whether the popup alert flag is set for an ETWS warning notification.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @return true if the message code indicates a popup alert should be displayed
+     */
+    private boolean isEtwsPopupAlert() {
+        return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
+    }
+
+    /**
+     * Return whether the emergency user alert flag is set for an ETWS warning notification.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @return true if the message code indicates an emergency user alert
+     */
+    private boolean isEtwsEmergencyUserAlert() {
+        return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0;
+    }
+
+    /**
+     * Returns the warning type for an ETWS warning notification.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24
+     */
+    private int getEtwsWarningType() {
+        return mMessageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING;
+    }
+
+    /**
+     * Returns the message class for a CMAS warning notification.
+     * This method assumes that the message ID has already been checked for CMAS type.
+     * @return the CMAS message class as defined in {@link SmsCbCmasInfo}
+     */
+    private int getCmasMessageClass() {
+        switch (mMessageIdentifier) {
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
+
+            default:
+                return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+        }
+    }
+
+    /**
+     * Returns the severity for a CMAS warning notification. This is only available for extreme
+     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+     * This method assumes that the message ID has already been checked for CMAS type.
+     * @return the CMAS severity as defined in {@link SmsCbCmasInfo}
+     */
+    private int getCmasSeverity() {
+        switch (mMessageIdentifier) {
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
+
+            default:
+                return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+        }
+    }
+
+    /**
+     * Returns the urgency for a CMAS warning notification. This is only available for extreme
+     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+     * This method assumes that the message ID has already been checked for CMAS type.
+     * @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
+     */
+    private int getCmasUrgency() {
+        switch (mMessageIdentifier) {
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
+
+            default:
+                return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+        }
+    }
+
+    /**
+     * Returns the certainty for a CMAS warning notification. This is only available for extreme
+     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+     * This method assumes that the message ID has already been checked for CMAS type.
+     * @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
+     */
+    private int getCmasCertainty() {
+        switch (mMessageIdentifier) {
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+                return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
+
+            default:
+                return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbHeader{GS=" + mGeographicalScope + ", serialNumber=0x" +
+                Integer.toHexString(mSerialNumber) +
+                ", messageIdentifier=0x" + Integer.toHexString(mMessageIdentifier) +
+                ", DCS=0x" + Integer.toHexString(mDataCodingScheme) +
+                ", page " + mPageIndex + " of " + mNrOfPages + '}';
+    }
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
new file mode 100644
index 0000000..582506a
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -0,0 +1,1369 @@
+/*
+ * 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 com.android.internal.telephony.gsm;
+
+import android.telephony.PhoneNumberUtils;
+import android.text.format.Time;
+import android.telephony.Rlog;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+
+import static com.android.internal.telephony.SmsConstants.MessageClass;
+import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
+import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+
+/**
+ * A Short Message Service message.
+ *
+ */
+public class SmsMessage extends SmsMessageBase {
+    static final String LOG_TAG = "SmsMessage";
+    private static final boolean VDBG = false;
+
+    private MessageClass messageClass;
+
+    /**
+     * TP-Message-Type-Indicator
+     * 9.2.3
+     */
+    private int mMti;
+
+    /** TP-Protocol-Identifier (TP-PID) */
+    private int mProtocolIdentifier;
+
+    // TP-Data-Coding-Scheme
+    // see TS 23.038
+    private int mDataCodingScheme;
+
+    // TP-Reply-Path
+    // e.g. 23.040 9.2.2.1
+    private boolean mReplyPathPresent = false;
+
+    /** The address of the receiver. */
+    private GsmSmsAddress mRecipientAddress;
+
+    /**
+     *  TP-Status - status of a previously submitted SMS.
+     *  This field applies to SMS-STATUS-REPORT messages.  0 indicates success;
+     *  see TS 23.040, 9.2.3.15 for description of other possible values.
+     */
+    private int mStatus;
+
+    /**
+     *  TP-Status - status of a previously submitted SMS.
+     *  This field is true iff the message is a SMS-STATUS-REPORT message.
+     */
+    private boolean mIsStatusReportMessage = false;
+
+    private int mVoiceMailCount = 0;
+
+    public static class SubmitPdu extends SubmitPduBase {
+    }
+
+    /**
+     * Create an SmsMessage from a raw PDU.
+     */
+    public static SmsMessage createFromPdu(byte[] pdu) {
+        try {
+            SmsMessage msg = new SmsMessage();
+            msg.parsePdu(pdu);
+            return msg;
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+            return null;
+        } catch (OutOfMemoryError e) {
+            Rlog.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
+            return null;
+        }
+    }
+
+    /**
+     * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated
+     * by TP_PID field set to value 0x40
+     */
+    public boolean isTypeZero() {
+        return (mProtocolIdentifier == 0x40);
+    }
+
+    /**
+     * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+     * +CMT unsolicited response (PDU mode, of course)
+     *  +CMT: [&lt;alpha>],<length><CR><LF><pdu>
+     *
+     * Only public for debugging
+     *
+     * {@hide}
+     */
+    public static SmsMessage newFromCMT(byte[] pdu) {
+        try {
+            SmsMessage msg = new SmsMessage();
+            msg.parsePdu(pdu);
+            return msg;
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+            return null;
+        }
+    }
+
+    /** @hide */
+    public static SmsMessage newFromCDS(byte[] pdu) {
+        try {
+            SmsMessage msg = new SmsMessage();
+            msg.parsePdu(pdu);
+            return msg;
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
+            return null;
+        }
+    }
+
+    /**
+     * Create an SmsMessage from an SMS EF record.
+     *
+     * @param index Index of SMS record. This should be index in ArrayList
+     *              returned by SmsManager.getAllMessagesFromSim + 1.
+     * @param data Record data.
+     * @return An SmsMessage representing the record.
+     *
+     * @hide
+     */
+    public static SmsMessage createFromEfRecord(int index, byte[] data) {
+        try {
+            SmsMessage msg = new SmsMessage();
+
+            msg.mIndexOnIcc = index;
+
+            // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
+            // or STORED_UNSENT
+            // See TS 51.011 10.5.3
+            if ((data[0] & 1) == 0) {
+                Rlog.w(LOG_TAG,
+                        "SMS parsing failed: Trying to parse a free record");
+                return null;
+            } else {
+                msg.mStatusOnIcc = data[0] & 0x07;
+            }
+
+            int size = data.length - 1;
+
+            // Note: Data may include trailing FF's.  That's OK; message
+            // should still parse correctly.
+            byte[] pdu = new byte[size];
+            System.arraycopy(data, 1, pdu, 0, size);
+            msg.parsePdu(pdu);
+            return msg;
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+            return null;
+        }
+    }
+
+    /**
+     * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
+     * length in bytes (not hex chars) less the SMSC header
+     */
+    public static int getTPLayerLengthForPDU(String pdu) {
+        int len = pdu.length() / 2;
+        int smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
+
+        return len - smscLen - 1;
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested, byte[] header) {
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
+                ENCODING_UNKNOWN, 0, 0);
+    }
+
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message using the
+     * specified encoding.
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @param encoding Encoding defined by constants in
+     *        com.android.internal.telephony.SmsConstants.ENCODING_*
+     * @param languageTable
+     * @param languageShiftTable
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested, byte[] header, int encoding,
+            int languageTable, int languageShiftTable) {
+
+        // Perform null parameter checks.
+        if (message == null || destinationAddress == null) {
+            return null;
+        }
+
+        if (encoding == ENCODING_UNKNOWN) {
+            // Find the best encoding to use
+            TextEncodingDetails ted = calculateLength(message, false);
+            encoding = ted.codeUnitSize;
+            languageTable = ted.languageTable;
+            languageShiftTable = ted.languageShiftTable;
+
+            if (encoding == ENCODING_7BIT &&
+                    (languageTable != 0 || languageShiftTable != 0)) {
+                if (header != null) {
+                    SmsHeader smsHeader = SmsHeader.fromByteArray(header);
+                    if (smsHeader.languageTable != languageTable
+                            || smsHeader.languageShiftTable != languageShiftTable) {
+                        Rlog.w(LOG_TAG, "Updating language table in SMS header: "
+                                + smsHeader.languageTable + " -> " + languageTable + ", "
+                                + smsHeader.languageShiftTable + " -> " + languageShiftTable);
+                        smsHeader.languageTable = languageTable;
+                        smsHeader.languageShiftTable = languageShiftTable;
+                        header = SmsHeader.toByteArray(smsHeader);
+                    }
+                } else {
+                    SmsHeader smsHeader = new SmsHeader();
+                    smsHeader.languageTable = languageTable;
+                    smsHeader.languageShiftTable = languageShiftTable;
+                    header = SmsHeader.toByteArray(smsHeader);
+                }
+            }
+        }
+
+        SubmitPdu ret = new SubmitPdu();
+        // MTI = SMS-SUBMIT, UDHI = header != null
+        byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+        ByteArrayOutputStream bo = getSubmitPduHead(
+                scAddress, destinationAddress, mtiByte,
+                statusReportRequested, ret);
+
+        // User Data (and length)
+        byte[] userData;
+        try {
+            if (encoding == ENCODING_7BIT) {
+                userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
+                        languageTable, languageShiftTable);
+            } else { //assume UCS-2
+                try {
+                    userData = encodeUCS2(message, header);
+                } catch(UnsupportedEncodingException uex) {
+                    Rlog.e(LOG_TAG,
+                            "Implausible UnsupportedEncodingException ",
+                            uex);
+                    return null;
+                }
+            }
+        } catch (EncodeException ex) {
+            // Encoding to the 7-bit alphabet failed. Let's see if we can
+            // send it as a UCS-2 encoded message
+            try {
+                userData = encodeUCS2(message, header);
+                encoding = ENCODING_16BIT;
+            } catch(UnsupportedEncodingException uex) {
+                Rlog.e(LOG_TAG,
+                        "Implausible UnsupportedEncodingException ",
+                        uex);
+                return null;
+            }
+        }
+
+        if (encoding == ENCODING_7BIT) {
+            if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
+                // Message too long
+                Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
+                return null;
+            }
+            // TP-Data-Coding-Scheme
+            // Default encoding, uncompressed
+            // To test writing messages to the SIM card, change this value 0x00
+            // to 0x12, which means "bits 1 and 0 contain message class, and the
+            // class is 2". Note that this takes effect for the sender. In other
+            // words, messages sent by the phone with this change will end up on
+            // the receiver's SIM card. You can then send messages to yourself
+            // (on a phone with this change) and they'll end up on the SIM card.
+            bo.write(0x00);
+        } else { // assume UCS-2
+            if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
+                // Message too long
+                Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
+                return null;
+            }
+            // TP-Data-Coding-Scheme
+            // UCS-2 encoding, uncompressed
+            bo.write(0x08);
+        }
+
+        // (no TP-Validity-Period)
+        bo.write(userData, 0, userData.length);
+        ret.encodedMessage = bo.toByteArray();
+        return ret;
+    }
+
+    /**
+     * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
+     *
+     * @return encoded message as UCS2
+     * @throws UnsupportedEncodingException
+     */
+    private static byte[] encodeUCS2(String message, byte[] header)
+        throws UnsupportedEncodingException {
+        byte[] userData, textPart;
+        textPart = message.getBytes("utf-16be");
+
+        if (header != null) {
+            // Need 1 byte for UDHL
+            userData = new byte[header.length + textPart.length + 1];
+
+            userData[0] = (byte)header.length;
+            System.arraycopy(header, 0, userData, 1, header.length);
+            System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
+        }
+        else {
+            userData = textPart;
+        }
+        byte[] ret = new byte[userData.length+1];
+        ret[0] = (byte) (userData.length & 0xff );
+        System.arraycopy(userData, 0, ret, 1, userData.length);
+        return ret;
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested) {
+
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+     *
+     * @param scAddress Service Centre address. null == use default
+     * @param destinationAddress the address of the destination for the message
+     * @param destinationPort the port to deliver the message to at the
+     *        destination
+     * @param data the data for the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, int destinationPort, byte[] data,
+            boolean statusReportRequested) {
+
+        SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+        portAddrs.destPort = destinationPort;
+        portAddrs.origPort = 0;
+        portAddrs.areEightBits = false;
+
+        SmsHeader smsHeader = new SmsHeader();
+        smsHeader.portAddrs = portAddrs;
+
+        byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
+
+        if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
+            Rlog.e(LOG_TAG, "SMS data message may only contain "
+                    + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
+            return null;
+        }
+
+        SubmitPdu ret = new SubmitPdu();
+        ByteArrayOutputStream bo = getSubmitPduHead(
+                scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
+                                                            // TP-UDHI = true
+                statusReportRequested, ret);
+
+        // TP-Data-Coding-Scheme
+        // No class, 8 bit data
+        bo.write(0x04);
+
+        // (no TP-Validity-Period)
+
+        // Total size
+        bo.write(data.length + smsHeaderData.length + 1);
+
+        // User data header
+        bo.write(smsHeaderData.length);
+        bo.write(smsHeaderData, 0, smsHeaderData.length);
+
+        // User data
+        bo.write(data, 0, data.length);
+
+        ret.encodedMessage = bo.toByteArray();
+        return ret;
+    }
+
+    /**
+     * Create the beginning of a SUBMIT PDU.  This is the part of the
+     * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
+     * one of which takes a byte array and the other of which takes a
+     * <code>String</code>.
+     *
+     * @param scAddress Service Centre address. null == use default
+     * @param destinationAddress the address of the destination for the message
+     * @param mtiByte
+     * @param ret <code>SubmitPdu</code> containing the encoded SC
+     *        address, if applicable, and the encoded message
+     */
+    private static ByteArrayOutputStream getSubmitPduHead(
+            String scAddress, String destinationAddress, byte mtiByte,
+            boolean statusReportRequested, SubmitPdu ret) {
+        ByteArrayOutputStream bo = new ByteArrayOutputStream(
+                MAX_USER_DATA_BYTES + 40);
+
+        // SMSC address with length octet, or 0
+        if (scAddress == null) {
+            ret.encodedScAddress = null;
+        } else {
+            ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
+                    scAddress);
+        }
+
+        // TP-Message-Type-Indicator (and friends)
+        if (statusReportRequested) {
+            // Set TP-Status-Report-Request bit.
+            mtiByte |= 0x20;
+            if (VDBG) Rlog.d(LOG_TAG, "SMS status report requested");
+        }
+        bo.write(mtiByte);
+
+        // space for TP-Message-Reference
+        bo.write(0);
+
+        byte[] daBytes;
+
+        daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
+
+        // destination address length in BCD digits, ignoring TON byte and pad
+        // TODO Should be better.
+        bo.write((daBytes.length - 1) * 2
+                - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
+
+        // destination address
+        bo.write(daBytes, 0, daBytes.length);
+
+        // TP-Protocol-Identifier
+        bo.write(0);
+        return bo;
+    }
+
+    private static class PduParser {
+        byte mPdu[];
+        int mCur;
+        SmsHeader mUserDataHeader;
+        byte[] mUserData;
+        int mUserDataSeptetPadding;
+
+        PduParser(byte[] pdu) {
+            mPdu = pdu;
+            mCur = 0;
+            mUserDataSeptetPadding = 0;
+        }
+
+        /**
+         * Parse and return the SC address prepended to SMS messages coming via
+         * the TS 27.005 / AT interface.  Returns null on invalid address
+         */
+        String getSCAddress() {
+            int len;
+            String ret;
+
+            // length of SC Address
+            len = getByte();
+
+            if (len == 0) {
+                // no SC address
+                ret = null;
+            } else {
+                // SC address
+                try {
+                    ret = PhoneNumberUtils
+                            .calledPartyBCDToString(mPdu, mCur, len);
+                } catch (RuntimeException tr) {
+                    Rlog.d(LOG_TAG, "invalid SC address: ", tr);
+                    ret = null;
+                }
+            }
+
+            mCur += len;
+
+            return ret;
+        }
+
+        /**
+         * returns non-sign-extended byte value
+         */
+        int getByte() {
+            return mPdu[mCur++] & 0xff;
+        }
+
+        /**
+         * Any address except the SC address (eg, originating address) See TS
+         * 23.040 9.1.2.5
+         */
+        GsmSmsAddress getAddress() {
+            GsmSmsAddress ret;
+
+            // "The Address-Length field is an integer representation of
+            // the number field, i.e. excludes any semi-octet containing only
+            // fill bits."
+            // The TOA field is not included as part of this
+            int addressLength = mPdu[mCur] & 0xff;
+            int lengthBytes = 2 + (addressLength + 1) / 2;
+
+            try {
+                ret = new GsmSmsAddress(mPdu, mCur, lengthBytes);
+            } catch (ParseException e) {
+                ret = null;
+                //This is caught by createFromPdu(byte[] pdu)
+                throw new RuntimeException(e.getMessage());
+            }
+
+            mCur += lengthBytes;
+
+            return ret;
+        }
+
+        /**
+         * Parses an SC timestamp and returns a currentTimeMillis()-style
+         * timestamp
+         */
+
+        long getSCTimestampMillis() {
+            // TP-Service-Centre-Time-Stamp
+            int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+            int month = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+            int day = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+            int hour = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+            int minute = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+            int second = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+
+            // For the timezone, the most significant bit of the
+            // least significant nibble is the sign byte
+            // (meaning the max range of this field is 79 quarter-hours,
+            // which is more than enough)
+
+            byte tzByte = mPdu[mCur++];
+
+            // Mask out sign bit.
+            int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
+
+            timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+
+            Time time = new Time(Time.TIMEZONE_UTC);
+
+            // It's 2006.  Should I really support years < 2000?
+            time.year = year >= 90 ? year + 1900 : year + 2000;
+            time.month = month - 1;
+            time.monthDay = day;
+            time.hour = hour;
+            time.minute = minute;
+            time.second = second;
+
+            // Timezone offset is in quarter hours.
+            return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
+        }
+
+        /**
+         * Pulls the user data out of the PDU, and separates the payload from
+         * the header if there is one.
+         *
+         * @param hasUserDataHeader true if there is a user data header
+         * @param dataInSeptets true if the data payload is in septets instead
+         *  of octets
+         * @return the number of septets or octets in the user data payload
+         */
+        int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
+            int offset = mCur;
+            int userDataLength = mPdu[offset++] & 0xff;
+            int headerSeptets = 0;
+            int userDataHeaderLength = 0;
+
+            if (hasUserDataHeader) {
+                userDataHeaderLength = mPdu[offset++] & 0xff;
+
+                byte[] udh = new byte[userDataHeaderLength];
+                System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength);
+                mUserDataHeader = SmsHeader.fromByteArray(udh);
+                offset += userDataHeaderLength;
+
+                int headerBits = (userDataHeaderLength + 1) * 8;
+                headerSeptets = headerBits / 7;
+                headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
+                mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
+            }
+
+            int bufferLen;
+            if (dataInSeptets) {
+                /*
+                 * Here we just create the user data length to be the remainder of
+                 * the pdu minus the user data header, since userDataLength means
+                 * the number of uncompressed septets.
+                 */
+                bufferLen = mPdu.length - offset;
+            } else {
+                /*
+                 * userDataLength is the count of octets, so just subtract the
+                 * user data header.
+                 */
+                bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
+                if (bufferLen < 0) {
+                    bufferLen = 0;
+                }
+            }
+
+            mUserData = new byte[bufferLen];
+            System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length);
+            mCur = offset;
+
+            if (dataInSeptets) {
+                // Return the number of septets
+                int count = userDataLength - headerSeptets;
+                // If count < 0, return 0 (means UDL was probably incorrect)
+                return count < 0 ? 0 : count;
+            } else {
+                // Return the number of octets
+                return mUserData.length;
+            }
+        }
+
+        /**
+         * Returns the user data payload, not including the headers
+         *
+         * @return the user data payload, not including the headers
+         */
+        byte[] getUserData() {
+            return mUserData;
+        }
+
+        /**
+         * Returns an object representing the user data headers
+         *
+         * {@hide}
+         */
+        SmsHeader getUserDataHeader() {
+            return mUserDataHeader;
+        }
+
+        /**
+         * Interprets the user data payload as packed GSM 7bit characters, and
+         * decodes them into a String.
+         *
+         * @param septetCount the number of septets in the user data payload
+         * @return a String with the decoded characters
+         */
+        String getUserDataGSM7Bit(int septetCount, int languageTable,
+                int languageShiftTable) {
+            String ret;
+
+            ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount,
+                    mUserDataSeptetPadding, languageTable, languageShiftTable);
+
+            mCur += (septetCount * 7) / 8;
+
+            return ret;
+        }
+
+        /**
+         * Interprets the user data payload as pack GSM 8-bit (a GSM alphabet string that's
+         * stored in 8-bit unpacked format) characters, and decodes them into a String.
+         *
+         * @param byteCount the number of byest in the user data payload
+         * @return a String with the decoded characters
+         */
+        String getUserDataGSM8bit(int byteCount) {
+            String ret;
+
+            ret = GsmAlphabet.gsm8BitUnpackedToString(mPdu, mCur, byteCount);
+
+            mCur += byteCount;
+
+            return ret;
+        }
+
+        /**
+         * Interprets the user data payload as UCS2 characters, and
+         * decodes them into a String.
+         *
+         * @param byteCount the number of bytes in the user data payload
+         * @return a String with the decoded characters
+         */
+        String getUserDataUCS2(int byteCount) {
+            String ret;
+
+            try {
+                ret = new String(mPdu, mCur, byteCount, "utf-16");
+            } catch (UnsupportedEncodingException ex) {
+                ret = "";
+                Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+            }
+
+            mCur += byteCount;
+            return ret;
+        }
+
+        /**
+         * Interprets the user data payload as KSC-5601 characters, and
+         * decodes them into a String.
+         *
+         * @param byteCount the number of bytes in the user data payload
+         * @return a String with the decoded characters
+         */
+        String getUserDataKSC5601(int byteCount) {
+            String ret;
+
+            try {
+                ret = new String(mPdu, mCur, byteCount, "KSC5601");
+            } catch (UnsupportedEncodingException ex) {
+                ret = "";
+                Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+            }
+
+            mCur += byteCount;
+            return ret;
+        }
+
+        boolean moreDataPresent() {
+            return (mPdu.length > mCur);
+        }
+    }
+
+    /**
+     * Calculates the number of SMS's required to encode the message body and
+     * the number of characters remaining until the next message.
+     *
+     * @param msgBody the message to encode
+     * @param use7bitOnly ignore (but still count) illegal characters if true
+     * @return TextEncodingDetails
+     */
+    public static TextEncodingDetails calculateLength(CharSequence msgBody,
+            boolean use7bitOnly) {
+        CharSequence newMsgBody = null;
+        Resources r = Resources.getSystem();
+        if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+            newMsgBody  = Sms7BitEncodingTranslator.translate(msgBody);
+        }
+        if (TextUtils.isEmpty(newMsgBody)) {
+            newMsgBody = msgBody;
+        }
+        TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(newMsgBody, use7bitOnly);
+        if (ted == null) {
+            return SmsMessageBase.calcUnicodeEncodingDetails(newMsgBody);
+        }
+        return ted;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getProtocolIdentifier() {
+        return mProtocolIdentifier;
+    }
+
+    /**
+     * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
+     * @return the TP-DCS field of the SMS header
+     */
+    int getDataCodingScheme() {
+        return mDataCodingScheme;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isReplace() {
+        return (mProtocolIdentifier & 0xc0) == 0x40
+                && (mProtocolIdentifier & 0x3f) > 0
+                && (mProtocolIdentifier & 0x3f) < 8;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCphsMwiMessage() {
+        return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear()
+                || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isMWIClearMessage() {
+        if (mIsMwi && !mMwiSense) {
+            return true;
+        }
+
+        return mOriginatingAddress != null
+                && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isMWISetMessage() {
+        if (mIsMwi && mMwiSense) {
+            return true;
+        }
+
+        return mOriginatingAddress != null
+                && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isMwiDontStore() {
+        if (mIsMwi && mMwiDontStore) {
+            return true;
+        }
+
+        if (isCphsMwiMessage()) {
+            // See CPHS 4.2 Section B.4.2.1
+            // If the user data is a single space char, do not store
+            // the message. Otherwise, store and display as usual
+            if (" ".equals(getMessageBody())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isStatusReportMessage() {
+        return mIsStatusReportMessage;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isReplyPathPresent() {
+        return mReplyPathPresent;
+    }
+
+    /**
+     * TS 27.005 3.1, &lt;pdu&gt; definition "In the case of SMS: 3GPP TS 24.011 [6]
+     * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
+     * ME/TA converts each octet of TP data unit into two IRA character long
+     * hex number (e.g. octet with integer value 42 is presented to TE as two
+     * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
+     * something else...
+     */
+    private void parsePdu(byte[] pdu) {
+        mPdu = pdu;
+        // Rlog.d(LOG_TAG, "raw sms message:");
+        // Rlog.d(LOG_TAG, s);
+
+        PduParser p = new PduParser(pdu);
+
+        mScAddress = p.getSCAddress();
+
+        if (mScAddress != null) {
+            if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress);
+        }
+
+        // TODO(mkf) support reply path, user data header indicator
+
+        // TP-Message-Type-Indicator
+        // 9.2.3
+        int firstByte = p.getByte();
+
+        mMti = firstByte & 0x3;
+        switch (mMti) {
+        // TP-Message-Type-Indicator
+        // 9.2.3
+        case 0:
+        case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
+                //This should be processed in the same way as MTI == 0 (Deliver)
+            parseSmsDeliver(p, firstByte);
+            break;
+        case 1:
+            parseSmsSubmit(p, firstByte);
+            break;
+        case 2:
+            parseSmsStatusReport(p, firstByte);
+            break;
+        default:
+            // TODO(mkf) the rest of these
+            throw new RuntimeException("Unsupported message type");
+        }
+    }
+
+    /**
+     * Parses a SMS-STATUS-REPORT message.
+     *
+     * @param p A PduParser, cued past the first byte.
+     * @param firstByte The first byte of the PDU, which contains MTI, etc.
+     */
+    private void parseSmsStatusReport(PduParser p, int firstByte) {
+        mIsStatusReportMessage = true;
+
+        // TP-Message-Reference
+        mMessageRef = p.getByte();
+        // TP-Recipient-Address
+        mRecipientAddress = p.getAddress();
+        // TP-Service-Centre-Time-Stamp
+        mScTimeMillis = p.getSCTimestampMillis();
+        p.getSCTimestampMillis();
+        // TP-Status
+        mStatus = p.getByte();
+
+        // The following are optional fields that may or may not be present.
+        if (p.moreDataPresent()) {
+            // TP-Parameter-Indicator
+            int extraParams = p.getByte();
+            int moreExtraParams = extraParams;
+            while ((moreExtraParams & 0x80) != 0) {
+                // We only know how to parse a few extra parameters, all
+                // indicated in the first TP-PI octet, so skip over any
+                // additional TP-PI octets.
+                moreExtraParams = p.getByte();
+            }
+            // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
+            // only process the byte if the reserved bits (bits3 to 6) are zero.
+            if ((extraParams & 0x78) == 0) {
+                // TP-Protocol-Identifier
+                if ((extraParams & 0x01) != 0) {
+                    mProtocolIdentifier = p.getByte();
+                }
+                // TP-Data-Coding-Scheme
+                if ((extraParams & 0x02) != 0) {
+                    mDataCodingScheme = p.getByte();
+                }
+                // TP-User-Data-Length (implies existence of TP-User-Data)
+                if ((extraParams & 0x04) != 0) {
+                    boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+                    parseUserData(p, hasUserDataHeader);
+                }
+            }
+        }
+    }
+
+    private void parseSmsDeliver(PduParser p, int firstByte) {
+        mReplyPathPresent = (firstByte & 0x80) == 0x80;
+
+        mOriginatingAddress = p.getAddress();
+
+        if (mOriginatingAddress != null) {
+            if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+                    + mOriginatingAddress.address);
+        }
+
+        // TP-Protocol-Identifier (TP-PID)
+        // TS 23.040 9.2.3.9
+        mProtocolIdentifier = p.getByte();
+
+        // TP-Data-Coding-Scheme
+        // see TS 23.038
+        mDataCodingScheme = p.getByte();
+
+        if (VDBG) {
+            Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+                    + " data coding scheme: " + mDataCodingScheme);
+        }
+
+        mScTimeMillis = p.getSCTimestampMillis();
+
+        if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
+
+        boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+
+        parseUserData(p, hasUserDataHeader);
+    }
+
+    /**
+     * Parses a SMS-SUBMIT message.
+     *
+     * @param p A PduParser, cued past the first byte.
+     * @param firstByte The first byte of the PDU, which contains MTI, etc.
+     */
+    private void parseSmsSubmit(PduParser p, int firstByte) {
+        mReplyPathPresent = (firstByte & 0x80) == 0x80;
+
+        // TP-MR (TP-Message Reference)
+        mMessageRef = p.getByte();
+
+        mRecipientAddress = p.getAddress();
+
+        if (mRecipientAddress != null) {
+            if (VDBG) Rlog.v(LOG_TAG, "SMS recipient address: " + mRecipientAddress.address);
+        }
+
+        // TP-Protocol-Identifier (TP-PID)
+        // TS 23.040 9.2.3.9
+        mProtocolIdentifier = p.getByte();
+
+        // TP-Data-Coding-Scheme
+        // see TS 23.038
+        mDataCodingScheme = p.getByte();
+
+        if (VDBG) {
+            Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+                    + " data coding scheme: " + mDataCodingScheme);
+        }
+
+        // TP-Validity-Period-Format
+        int validityPeriodLength = 0;
+        int validityPeriodFormat = ((firstByte>>3) & 0x3);
+        if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/
+        {
+            validityPeriodLength = 0;
+        }
+        else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/
+        {
+            validityPeriodLength = 1;
+        }
+        else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/
+        {
+            validityPeriodLength = 7;
+        }
+
+        // TP-Validity-Period is not used on phone, so just ignore it for now.
+        while (validityPeriodLength-- > 0)
+        {
+            p.getByte();
+        }
+
+        boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+
+        parseUserData(p, hasUserDataHeader);
+    }
+
+    /**
+     * Parses the User Data of an SMS.
+     *
+     * @param p The current PduParser.
+     * @param hasUserDataHeader Indicates whether a header is present in the
+     *                          User Data.
+     */
+    private void parseUserData(PduParser p, boolean hasUserDataHeader) {
+        boolean hasMessageClass = false;
+        boolean userDataCompressed = false;
+
+        int encodingType = ENCODING_UNKNOWN;
+
+        // Look up the data encoding scheme
+        if ((mDataCodingScheme & 0x80) == 0) {
+            userDataCompressed = (0 != (mDataCodingScheme & 0x20));
+            hasMessageClass = (0 != (mDataCodingScheme & 0x10));
+
+            if (userDataCompressed) {
+                Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
+                        + "(compression) " + (mDataCodingScheme & 0xff));
+            } else {
+                switch ((mDataCodingScheme >> 2) & 0x3) {
+                case 0: // GSM 7 bit default alphabet
+                    encodingType = ENCODING_7BIT;
+                    break;
+
+                case 2: // UCS 2 (16bit)
+                    encodingType = ENCODING_16BIT;
+                    break;
+
+                case 1: // 8 bit data
+                    //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
+                    //that's stored in 8-bit unpacked format) characters.
+                    Resources r = Resources.getSystem();
+                    if (r.getBoolean(com.android.internal.
+                            R.bool.config_sms_decode_gsm_8bit_data)) {
+                        encodingType = ENCODING_8BIT;
+                        break;
+                    }
+
+                case 3: // reserved
+                    Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+                            + (mDataCodingScheme & 0xff));
+                    encodingType = ENCODING_8BIT;
+                    break;
+                }
+            }
+        } else if ((mDataCodingScheme & 0xf0) == 0xf0) {
+            hasMessageClass = true;
+            userDataCompressed = false;
+
+            if (0 == (mDataCodingScheme & 0x04)) {
+                // GSM 7 bit default alphabet
+                encodingType = ENCODING_7BIT;
+            } else {
+                // 8 bit data
+                encodingType = ENCODING_8BIT;
+            }
+        } else if ((mDataCodingScheme & 0xF0) == 0xC0
+                || (mDataCodingScheme & 0xF0) == 0xD0
+                || (mDataCodingScheme & 0xF0) == 0xE0) {
+            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+
+            // 0xC0 == 7 bit, don't store
+            // 0xD0 == 7 bit, store
+            // 0xE0 == UCS-2, store
+
+            if ((mDataCodingScheme & 0xF0) == 0xE0) {
+                encodingType = ENCODING_16BIT;
+            } else {
+                encodingType = ENCODING_7BIT;
+            }
+
+            userDataCompressed = false;
+            boolean active = ((mDataCodingScheme & 0x08) == 0x08);
+            // bit 0x04 reserved
+
+            // VM - If TP-UDH is present, these values will be overwritten
+            if ((mDataCodingScheme & 0x03) == 0x00) {
+                mIsMwi = true; /* Indicates vmail */
+                mMwiSense = active;/* Indicates vmail notification set/clear */
+                mMwiDontStore = ((mDataCodingScheme & 0xF0) == 0xC0);
+
+                /* Set voice mail count based on notification bit */
+                if (active == true) {
+                    mVoiceMailCount = -1; // unknown number of messages waiting
+                } else {
+                    mVoiceMailCount = 0; // no unread messages
+                }
+
+                Rlog.w(LOG_TAG, "MWI in DCS for Vmail. DCS = "
+                        + (mDataCodingScheme & 0xff) + " Dont store = "
+                        + mMwiDontStore + " vmail count = " + mVoiceMailCount);
+
+            } else {
+                mIsMwi = false;
+                Rlog.w(LOG_TAG, "MWI in DCS for fax/email/other: "
+                        + (mDataCodingScheme & 0xff));
+            }
+        } else if ((mDataCodingScheme & 0xC0) == 0x80) {
+            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+            // 0x80..0xBF == Reserved coding groups
+            if (mDataCodingScheme == 0x84) {
+                // This value used for KSC5601 by carriers in Korea.
+                encodingType = ENCODING_KSC5601;
+            } else {
+                Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
+                        + (mDataCodingScheme & 0xff));
+            }
+        } else {
+            Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
+                    + (mDataCodingScheme & 0xff));
+        }
+
+        // set both the user data and the user data header.
+        int count = p.constructUserData(hasUserDataHeader,
+                encodingType == ENCODING_7BIT);
+        this.mUserData = p.getUserData();
+        this.mUserDataHeader = p.getUserDataHeader();
+
+        /*
+         * Look for voice mail indication in TP_UDH TS23.040 9.2.3.24
+         * ieid = 1 (0x1) (SPECIAL_SMS_MSG_IND)
+         * ieidl =2 octets
+         * ieda msg_ind_type = 0x00 (voice mail; discard sms )or
+         *                   = 0x80 (voice mail; store sms)
+         * msg_count = 0x00 ..0xFF
+         */
+        if (hasUserDataHeader && (mUserDataHeader.specialSmsMsgList.size() != 0)) {
+            for (SmsHeader.SpecialSmsMsg msg : mUserDataHeader.specialSmsMsgList) {
+                int msgInd = msg.msgIndType & 0xff;
+                /*
+                 * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+                 * bits 1 0 : basic message indication type
+                 * bits 4 3 2 : extended message indication type
+                 * bits 6 5 : Profile id bit 7 storage type
+                 */
+                if ((msgInd == 0) || (msgInd == 0x80)) {
+                    mIsMwi = true;
+                    if (msgInd == 0x80) {
+                        /* Store message because TP_UDH indicates so*/
+                        mMwiDontStore = false;
+                    } else if (mMwiDontStore == false) {
+                        /* Storage bit is not set by TP_UDH
+                         * Check for conflict
+                         * between message storage bit in TP_UDH
+                         * & DCS. The message shall be stored if either of
+                         * the one indicates so.
+                         * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+                         */
+                        if (!((((mDataCodingScheme & 0xF0) == 0xD0)
+                               || ((mDataCodingScheme & 0xF0) == 0xE0))
+                               && ((mDataCodingScheme & 0x03) == 0x00))) {
+                            /* Even DCS did not have voice mail with Storage bit
+                             * 3GPP TS 23.038 V7.0.0 section 4
+                             * So clear this flag*/
+                            mMwiDontStore = true;
+                        }
+                    }
+
+                    mVoiceMailCount = msg.msgCount & 0xff;
+
+                    /*
+                     * In the event of a conflict between message count setting
+                     * and DCS then the Message Count in the TP-UDH shall
+                     * override the indication in the TP-DCS. Set voice mail
+                     * notification based on count in TP-UDH
+                     */
+                    if (mVoiceMailCount > 0)
+                        mMwiSense = true;
+                    else
+                        mMwiSense = false;
+
+                    Rlog.w(LOG_TAG, "MWI in TP-UDH for Vmail. Msg Ind = " + msgInd
+                            + " Dont store = " + mMwiDontStore + " Vmail count = "
+                            + mVoiceMailCount);
+
+                    /*
+                     * There can be only one IE for each type of message
+                     * indication in TP_UDH. In the event they are duplicated
+                     * last occurence will be used. Hence the for loop
+                     */
+                } else {
+                    Rlog.w(LOG_TAG, "TP_UDH fax/email/"
+                            + "extended msg/multisubscriber profile. Msg Ind = " + msgInd);
+                }
+            } // end of for
+        } // end of if UDH
+
+        switch (encodingType) {
+        case ENCODING_UNKNOWN:
+            mMessageBody = null;
+            break;
+
+        case ENCODING_8BIT:
+            //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
+            //that's stored in 8-bit unpacked format) characters.
+            Resources r = Resources.getSystem();
+            if (r.getBoolean(com.android.internal.
+                    R.bool.config_sms_decode_gsm_8bit_data)) {
+                mMessageBody = p.getUserDataGSM8bit(count);
+            } else {
+                mMessageBody = null;
+            }
+            break;
+
+        case ENCODING_7BIT:
+            mMessageBody = p.getUserDataGSM7Bit(count,
+                    hasUserDataHeader ? mUserDataHeader.languageTable : 0,
+                    hasUserDataHeader ? mUserDataHeader.languageShiftTable : 0);
+            break;
+
+        case ENCODING_16BIT:
+            mMessageBody = p.getUserDataUCS2(count);
+            break;
+
+        case ENCODING_KSC5601:
+            mMessageBody = p.getUserDataKSC5601(count);
+            break;
+        }
+
+        if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'");
+
+        if (mMessageBody != null) {
+            parseMessageBody();
+        }
+
+        if (!hasMessageClass) {
+            messageClass = MessageClass.UNKNOWN;
+        } else {
+            switch (mDataCodingScheme & 0x3) {
+            case 0:
+                messageClass = MessageClass.CLASS_0;
+                break;
+            case 1:
+                messageClass = MessageClass.CLASS_1;
+                break;
+            case 2:
+                messageClass = MessageClass.CLASS_2;
+                break;
+            case 3:
+                messageClass = MessageClass.CLASS_3;
+                break;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public MessageClass getMessageClass() {
+        return messageClass;
+    }
+
+    /**
+     * Returns true if this is a (U)SIM data download type SM.
+     * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
+     *
+     * @return true if this is a USIM data download message; false otherwise
+     */
+    boolean isUsimDataDownload() {
+        return messageClass == MessageClass.CLASS_2 &&
+                (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
+    }
+
+    public int getNumOfVoicemails() {
+        /*
+         * Order of priority if multiple indications are present is 1.UDH,
+         *      2.DCS, 3.CPHS.
+         * Voice mail count if voice mail present indication is
+         * received
+         *  1. UDH (or both UDH & DCS): mVoiceMailCount = 0 to 0xff. Ref[TS 23. 040]
+         *  2. DCS only: count is unknown mVoiceMailCount= -1
+         *  3. CPHS only: count is unknown mVoiceMailCount = 0xff. Ref[GSM-BTR-1-4700]
+         * Voice mail clear, mVoiceMailCount = 0.
+         */
+        if ((!mIsMwi) && isCphsMwiMessage()) {
+            if (mOriginatingAddress != null
+                    && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet()) {
+                mVoiceMailCount = 0xff;
+            } else {
+                mVoiceMailCount = 0;
+            }
+            Rlog.v(LOG_TAG, "CPHS voice mail message");
+        }
+        return mVoiceMailCount;
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
new file mode 100644
index 0000000..67de87f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -0,0 +1,570 @@
+/*
+ * 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 com.android.internal.telephony.uicc;
+
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.GsmAlphabet;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Various methods, useful for dealing with SIM data.
+ */
+public class IccUtils {
+    static final String LOG_TAG="IccUtils";
+
+    /**
+     * Many fields in GSM SIM's are stored as nibble-swizzled BCD
+     *
+     * Assumes left-justified field that may be padded right with 0xf
+     * values.
+     *
+     * Stops on invalid BCD value, returning string so far
+     */
+    public static String
+    bcdToString(byte[] data, int offset, int length) {
+        StringBuilder ret = new StringBuilder(length*2);
+
+        for (int i = offset ; i < offset + length ; i++) {
+            int v;
+
+            v = data[i] & 0xf;
+            if (v > 9)  break;
+            ret.append((char)('0' + v));
+
+            v = (data[i] >> 4) & 0xf;
+            // Some PLMNs have 'f' as high nibble, ignore it
+            if (v == 0xf) continue;
+            if (v > 9)  break;
+            ret.append((char)('0' + v));
+        }
+
+        return ret.toString();
+    }
+
+    /**
+     * PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3
+     * Returns a concatenated string of MCC+MNC, stripping
+     * a trailing character for a 2-digit MNC
+     */
+    public static String bcdPlmnToString(byte[] data, int offset) {
+        if (offset + 3 > data.length) {
+            return null;
+        }
+        byte[] trans = new byte[3];
+        trans[0] = (byte) ((data[0 + offset] << 4) | ((data[0 + offset] >> 4) & 0xF));
+        trans[1] = (byte) ((data[1 + offset] << 4) | (data[2 + offset] & 0xF));
+        trans[2] = (byte) ((data[2 + offset] & 0xF0) | ((data[1 + offset] >> 4) & 0xF));
+        String ret = bytesToHexString(trans);
+
+        // For a 2-digit MNC we trim the trailing 'f'
+        if (ret.endsWith("f")) {
+            ret = ret.substring(0, ret.length() - 1);
+        }
+        return ret;
+    }
+
+    /**
+     * Some fields (like ICC ID) in GSM SIMs are stored as nibble-swizzled BCH
+     */
+    public static String
+    bchToString(byte[] data, int offset, int length) {
+        StringBuilder ret = new StringBuilder(length*2);
+
+        for (int i = offset ; i < offset + length ; i++) {
+            int v;
+
+            v = data[i] & 0xf;
+            ret.append("0123456789abcdef".charAt(v));
+
+            v = (data[i] >> 4) & 0xf;
+            ret.append("0123456789abcdef".charAt(v));
+        }
+
+        return ret.toString();
+    }
+
+    /**
+     * Decode cdma byte into String.
+     */
+    public static String
+    cdmaBcdToString(byte[] data, int offset, int length) {
+        StringBuilder ret = new StringBuilder(length);
+
+        int count = 0;
+        for (int i = offset; count < length; i++) {
+            int v;
+            v = data[i] & 0xf;
+            if (v > 9)  v = 0;
+            ret.append((char)('0' + v));
+
+            if (++count == length) break;
+
+            v = (data[i] >> 4) & 0xf;
+            if (v > 9)  v = 0;
+            ret.append((char)('0' + v));
+            ++count;
+        }
+        return ret.toString();
+    }
+
+    /**
+     * Decodes a GSM-style BCD byte, returning an int ranging from 0-99.
+     *
+     * In GSM land, the least significant BCD digit is stored in the most
+     * significant nibble.
+     *
+     * Out-of-range digits are treated as 0 for the sake of the time stamp,
+     * because of this:
+     *
+     * TS 23.040 section 9.2.3.11
+     * "if the MS receives a non-integer value in the SCTS, it shall
+     * assume the digit is set to 0 but shall store the entire field
+     * exactly as received"
+     */
+    public static int
+    gsmBcdByteToInt(byte b) {
+        int ret = 0;
+
+        // treat out-of-range BCD values as 0
+        if ((b & 0xf0) <= 0x90) {
+            ret = (b >> 4) & 0xf;
+        }
+
+        if ((b & 0x0f) <= 0x09) {
+            ret +=  (b & 0xf) * 10;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Decodes a CDMA style BCD byte like {@link #gsmBcdByteToInt}, but
+     * opposite nibble format. The least significant BCD digit
+     * is in the least significant nibble and the most significant
+     * is in the most significant nibble.
+     */
+    public static int
+    cdmaBcdByteToInt(byte b) {
+        int ret = 0;
+
+        // treat out-of-range BCD values as 0
+        if ((b & 0xf0) <= 0x90) {
+            ret = ((b >> 4) & 0xf) * 10;
+        }
+
+        if ((b & 0x0f) <= 0x09) {
+            ret +=  (b & 0xf);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Decodes a string field that's formatted like the EF[ADN] alpha
+     * identifier
+     *
+     * From TS 51.011 10.5.1:
+     *   Coding:
+     *       this alpha tagging shall use either
+     *      -    the SMS default 7 bit coded alphabet as defined in
+     *          TS 23.038 [12] with bit 8 set to 0. The alpha identifier
+     *          shall be left justified. Unused bytes shall be set to 'FF'; or
+     *      -    one of the UCS2 coded options as defined in annex B.
+     *
+     * Annex B from TS 11.11 V8.13.0:
+     *      1)  If the first octet in the alpha string is '80', then the
+     *          remaining octets are 16 bit UCS2 characters ...
+     *      2)  if the first octet in the alpha string is '81', then the
+     *          second octet contains a value indicating the number of
+     *          characters in the string, and the third octet contains an
+     *          8 bit number which defines bits 15 to 8 of a 16 bit
+     *          base pointer, where bit 16 is set to zero and bits 7 to 1
+     *          are also set to zero.  These sixteen bits constitute a
+     *          base pointer to a "half page" in the UCS2 code space, to be
+     *          used with some or all of the remaining octets in the string.
+     *          The fourth and subsequent octets contain codings as follows:
+     *          If bit 8 of the octet is set to zero, the remaining 7 bits
+     *          of the octet contain a GSM Default Alphabet character,
+     *          whereas if bit 8 of the octet is set to one, then the
+     *          remaining seven bits are an offset value added to the
+     *          16 bit base pointer defined earlier...
+     *      3)  If the first octet of the alpha string is set to '82', then
+     *          the second octet contains a value indicating the number of
+     *          characters in the string, and the third and fourth octets
+     *          contain a 16 bit number which defines the complete 16 bit
+     *          base pointer to a "half page" in the UCS2 code space...
+     */
+    public static String
+    adnStringFieldToString(byte[] data, int offset, int length) {
+        if (length == 0) {
+            return "";
+        }
+        if (length >= 1) {
+            if (data[offset] == (byte) 0x80) {
+                int ucslen = (length - 1) / 2;
+                String ret = null;
+
+                try {
+                    ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
+                } catch (UnsupportedEncodingException ex) {
+                    Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException",
+                          ex);
+                }
+
+                if (ret != null) {
+                    // trim off trailing FFFF characters
+
+                    ucslen = ret.length();
+                    while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF')
+                        ucslen--;
+
+                    return ret.substring(0, ucslen);
+                }
+            }
+        }
+
+        boolean isucs2 = false;
+        char base = '\0';
+        int len = 0;
+
+        if (length >= 3 && data[offset] == (byte) 0x81) {
+            len = data[offset + 1] & 0xFF;
+            if (len > length - 3)
+                len = length - 3;
+
+            base = (char) ((data[offset + 2] & 0xFF) << 7);
+            offset += 3;
+            isucs2 = true;
+        } else if (length >= 4 && data[offset] == (byte) 0x82) {
+            len = data[offset + 1] & 0xFF;
+            if (len > length - 4)
+                len = length - 4;
+
+            base = (char) (((data[offset + 2] & 0xFF) << 8) |
+                            (data[offset + 3] & 0xFF));
+            offset += 4;
+            isucs2 = true;
+        }
+
+        if (isucs2) {
+            StringBuilder ret = new StringBuilder();
+
+            while (len > 0) {
+                // UCS2 subset case
+
+                if (data[offset] < 0) {
+                    ret.append((char) (base + (data[offset] & 0x7F)));
+                    offset++;
+                    len--;
+                }
+
+                // GSM character set case
+
+                int count = 0;
+                while (count < len && data[offset + count] >= 0)
+                    count++;
+
+                ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
+                           offset, count));
+
+                offset += count;
+                len -= count;
+            }
+
+            return ret.toString();
+        }
+
+        Resources resource = Resources.getSystem();
+        String defaultCharset = "";
+        try {
+            defaultCharset =
+                    resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset);
+        } catch (NotFoundException e) {
+            // Ignore Exception and defaultCharset is set to a empty string.
+        }
+        return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
+    }
+
+    static int
+    hexCharToInt(char c) {
+        if (c >= '0' && c <= '9') return (c - '0');
+        if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
+        if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+
+        throw new RuntimeException ("invalid hex char '" + c + "'");
+    }
+
+    /**
+     * Converts a hex String to a byte array.
+     *
+     * @param s A string of hexadecimal characters, must be an even number of
+     *          chars long
+     *
+     * @return byte array representation
+     *
+     * @throws RuntimeException on invalid format
+     */
+    public static byte[]
+    hexStringToBytes(String s) {
+        byte[] ret;
+
+        if (s == null) return null;
+
+        int sz = s.length();
+
+        ret = new byte[sz/2];
+
+        for (int i=0 ; i <sz ; i+=2) {
+            ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4)
+                                | hexCharToInt(s.charAt(i+1)));
+        }
+
+        return ret;
+    }
+
+
+    /**
+     * Converts a byte array into a String of hexadecimal characters.
+     *
+     * @param bytes an array of bytes
+     *
+     * @return hex string representation of bytes array
+     */
+    public static String
+    bytesToHexString(byte[] bytes) {
+        if (bytes == null) return null;
+
+        StringBuilder ret = new StringBuilder(2*bytes.length);
+
+        for (int i = 0 ; i < bytes.length ; i++) {
+            int b;
+
+            b = 0x0f & (bytes[i] >> 4);
+
+            ret.append("0123456789abcdef".charAt(b));
+
+            b = 0x0f & bytes[i];
+
+            ret.append("0123456789abcdef".charAt(b));
+        }
+
+        return ret.toString();
+    }
+
+
+    /**
+     * Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string
+     * "offset" points to "octet 3", the coding scheme byte
+     * empty string returned on decode error
+     */
+    public static String
+    networkNameToString(byte[] data, int offset, int length) {
+        String ret;
+
+        if ((data[offset] & 0x80) != 0x80 || length < 1) {
+            return "";
+        }
+
+        switch ((data[offset] >>> 4) & 0x7) {
+            case 0:
+                // SMS character set
+                int countSeptets;
+                int unusedBits = data[offset] & 7;
+                countSeptets = (((length - 1) * 8) - unusedBits) / 7 ;
+                ret =  GsmAlphabet.gsm7BitPackedToString(data, offset + 1, countSeptets);
+            break;
+            case 1:
+                // UCS2
+                try {
+                    ret = new String(data,
+                            offset + 1, length - 1, "utf-16");
+                } catch (UnsupportedEncodingException ex) {
+                    ret = "";
+                    Rlog.e(LOG_TAG,"implausible UnsupportedEncodingException", ex);
+                }
+            break;
+
+            // unsupported encoding
+            default:
+                ret = "";
+            break;
+        }
+
+        // "Add CI"
+        // "The MS should add the letters for the Country's Initials and
+        //  a separator (e.g. a space) to the text string"
+
+        if ((data[offset] & 0x40) != 0) {
+            // FIXME(mkf) add country initials here
+
+        }
+
+        return ret;
+    }
+
+    /**
+     * Convert a TS 131.102 image instance of code scheme '11' into Bitmap
+     * @param data The raw data
+     * @param length The length of image body
+     * @return The bitmap
+     */
+    public static Bitmap parseToBnW(byte[] data, int length){
+        int valueIndex = 0;
+        int width = data[valueIndex++] & 0xFF;
+        int height = data[valueIndex++] & 0xFF;
+        int numOfPixels = width*height;
+
+        int[] pixels = new int[numOfPixels];
+
+        int pixelIndex = 0;
+        int bitIndex = 7;
+        byte currentByte = 0x00;
+        while (pixelIndex < numOfPixels) {
+            // reassign data and index for every byte (8 bits).
+            if (pixelIndex % 8 == 0) {
+                currentByte = data[valueIndex++];
+                bitIndex = 7;
+            }
+            pixels[pixelIndex++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01);
+        }
+
+        if (pixelIndex != numOfPixels) {
+            Rlog.e(LOG_TAG, "parse end and size error");
+        }
+        return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
+    }
+
+    private static int bitToRGB(int bit){
+        if(bit == 1){
+            return Color.WHITE;
+        } else {
+            return Color.BLACK;
+        }
+    }
+
+    /**
+     * a TS 131.102 image instance of code scheme '11' into color Bitmap
+     *
+     * @param data The raw data
+     * @param length the length of image body
+     * @param transparency with or without transparency
+     * @return The color bitmap
+     */
+    public static Bitmap parseToRGB(byte[] data, int length,
+            boolean transparency) {
+        int valueIndex = 0;
+        int width = data[valueIndex++] & 0xFF;
+        int height = data[valueIndex++] & 0xFF;
+        int bits = data[valueIndex++] & 0xFF;
+        int colorNumber = data[valueIndex++] & 0xFF;
+        int clutOffset = ((data[valueIndex++] & 0xFF) << 8)
+                | (data[valueIndex++] & 0xFF);
+
+        int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber);
+        if (true == transparency) {
+            colorIndexArray[colorNumber - 1] = Color.TRANSPARENT;
+        }
+
+        int[] resultArray = null;
+        if (0 == (8 % bits)) {
+            resultArray = mapTo2OrderBitColor(data, valueIndex,
+                    (width * height), colorIndexArray, bits);
+        } else {
+            resultArray = mapToNon2OrderBitColor(data, valueIndex,
+                    (width * height), colorIndexArray, bits);
+        }
+
+        return Bitmap.createBitmap(resultArray, width, height,
+                Bitmap.Config.RGB_565);
+    }
+
+    private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex,
+            int length, int[] colorArray, int bits) {
+        if (0 != (8 % bits)) {
+            Rlog.e(LOG_TAG, "not event number of color");
+            return mapToNon2OrderBitColor(data, valueIndex, length, colorArray,
+                    bits);
+        }
+
+        int mask = 0x01;
+        switch (bits) {
+        case 1:
+            mask = 0x01;
+            break;
+        case 2:
+            mask = 0x03;
+            break;
+        case 4:
+            mask = 0x0F;
+            break;
+        case 8:
+            mask = 0xFF;
+            break;
+        }
+
+        int[] resultArray = new int[length];
+        int resultIndex = 0;
+        int run = 8 / bits;
+        while (resultIndex < length) {
+            byte tempByte = data[valueIndex++];
+            for (int runIndex = 0; runIndex < run; ++runIndex) {
+                int offset = run - runIndex - 1;
+                resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits))
+                        & mask];
+            }
+        }
+        return resultArray;
+    }
+
+    private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex,
+            int length, int[] colorArray, int bits) {
+        if (0 == (8 % bits)) {
+            Rlog.e(LOG_TAG, "not odd number of color");
+            return mapTo2OrderBitColor(data, valueIndex, length, colorArray,
+                    bits);
+        }
+
+        int[] resultArray = new int[length];
+        // TODO fix me:
+        return resultArray;
+    }
+
+    private static int[] getCLUT(byte[] rawData, int offset, int number) {
+        if (null == rawData) {
+            return null;
+        }
+
+        int[] result = new int[number];
+        int endIndex = offset + (number * 3); // 1 color use 3 bytes
+        int valueIndex = offset;
+        int colorIndex = 0;
+        int alpha = 0xff << 24;
+        do {
+            result[colorIndex++] = alpha
+                    | ((rawData[valueIndex++] & 0xFF) << 16)
+                    | ((rawData[valueIndex++] & 0xFF) << 8)
+                    | ((rawData[valueIndex++] & 0xFF));
+        } while (valueIndex < endIndex);
+        return result;
+    }
+}
diff --git a/test-runner/src/android/test/AndroidTestRunner.java b/test-runner/src/android/test/AndroidTestRunner.java
index 50eaafb..7313a28 100644
--- a/test-runner/src/android/test/AndroidTestRunner.java
+++ b/test-runner/src/android/test/AndroidTestRunner.java
@@ -20,8 +20,7 @@
 import android.content.Context;
 import android.os.PerformanceCollector.PerformanceResultsWriter;
 
-import com.google.android.collect.Lists;
-
+import java.util.ArrayList;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestListener;
@@ -48,7 +47,7 @@
     private Context mContext;
     private boolean mSkipExecution = false;
 
-    private List<TestListener> mTestListeners = Lists.newArrayList();
+    private List<TestListener> mTestListeners = new ArrayList<>();
     private Instrumentation mInstrumentation;
     private PerformanceResultsWriter mPerfWriter;
 
@@ -58,7 +57,8 @@
 
         if (shouldRunSingleTestMethod(testMethodName, testClass)) {
             TestCase testCase = buildSingleTestMethod(testClass, testMethodName);
-            mTestCases = Lists.newArrayList(testCase);
+            mTestCases = new ArrayList<>();
+            mTestCases.add(testCase);
             mTestClassName = testClass.getSimpleName();
         } else {
             setTest(getTest(testClass), testClass);
diff --git a/test-runner/src/android/test/ClassPathPackageInfo.java b/test-runner/src/android/test/ClassPathPackageInfo.java
index 1ab7c7f..2cf76af 100644
--- a/test-runner/src/android/test/ClassPathPackageInfo.java
+++ b/test-runner/src/android/test/ClassPathPackageInfo.java
@@ -16,9 +16,8 @@
 
 package android.test;
 
-import com.google.android.collect.Sets;
-
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -44,7 +43,7 @@
     }
 
     public Set<ClassPathPackageInfo> getSubpackages() {
-        Set<ClassPathPackageInfo> info = Sets.newHashSet();
+        Set<ClassPathPackageInfo> info = new HashSet<>();
         for (String name : subpackageNames) {
             info.add(source.getPackageInfo(name));
         }
@@ -52,7 +51,7 @@
     }
 
     public Set<Class<?>> getTopLevelClassesRecursive() {
-        Set<Class<?>> set = Sets.newHashSet();
+        Set<Class<?>> set = new HashSet<>();
         addTopLevelClassesTo(set);
         return set;
     }
diff --git a/test-runner/src/android/test/ClassPathPackageInfoSource.java b/test-runner/src/android/test/ClassPathPackageInfoSource.java
index 89bb494..9bcc25a 100644
--- a/test-runner/src/android/test/ClassPathPackageInfoSource.java
+++ b/test-runner/src/android/test/ClassPathPackageInfoSource.java
@@ -17,13 +17,13 @@
 package android.test;
 
 import android.util.Log;
-import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
 import dalvik.system.DexFile;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
@@ -57,7 +57,7 @@
     private static String[] apkPaths;
 
     // A cache of jar file contents
-    private final Map<File, Set<String>> jarFiles = Maps.newHashMap();
+    private final Map<File, Set<String>> jarFiles = new HashMap<>();
     private ClassLoader classLoader;
 
     ClassPathPackageInfoSource() {
@@ -76,7 +76,7 @@
     private ClassPathPackageInfo createPackageInfo(String packageName) {
         Set<String> subpackageNames = new TreeSet<String>();
         Set<String> classNames = new TreeSet<String>();
-        Set<Class<?>> topLevelClasses = Sets.newHashSet();
+        Set<Class<?>> topLevelClasses = new HashSet<>();
         findClasses(packageName, classNames, subpackageNames);
         for (String className : classNames) {
             if (className.endsWith(".R") || className.endsWith(".Manifest")) {
@@ -248,7 +248,7 @@
             throws IOException {
         Set<String> entryNames = jarFiles.get(jarFile);
         if (entryNames == null) {
-            entryNames = Sets.newHashSet();
+            entryNames = new HashSet<>();
             ZipFile zipFile = new ZipFile(jarFile);
             Enumeration<? extends ZipEntry> entries = zipFile.entries();
             while (entries.hasMoreElements()) {
diff --git a/test-runner/src/android/test/DatabaseTestUtils.java b/test-runner/src/android/test/DatabaseTestUtils.java
index 42ef48b..1980d92 100644
--- a/test-runner/src/android/test/DatabaseTestUtils.java
+++ b/test-runner/src/android/test/DatabaseTestUtils.java
@@ -16,11 +16,10 @@
 
 package android.test;
 
-import com.google.android.collect.Sets;
-
 import android.database.sqlite.SQLiteDatabase;
 import android.database.Cursor;
 
+import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -42,7 +41,7 @@
     }
 
     private static Set<String> getSchemaSet(SQLiteDatabase db) {
-        Set<String> schemaSet = Sets.newHashSet();
+        Set<String> schemaSet = new HashSet<>();
 
         Cursor entityCursor = db.rawQuery("SELECT sql FROM sqlite_master", null);
         try {
diff --git a/test-runner/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java
index 3abf38f..0b77c00 100644
--- a/test-runner/src/android/test/IsolatedContext.java
+++ b/test-runner/src/android/test/IsolatedContext.java
@@ -16,8 +16,6 @@
 
 package android.test;
 
-import com.google.android.collect.Lists;
-
 import android.accounts.AccountManager;
 import android.accounts.AccountManagerCallback;
 import android.accounts.AccountManagerFuture;
@@ -38,6 +36,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.List;
 
@@ -55,7 +54,7 @@
     private ContentResolver mResolver;
     private final MockAccountManager mMockAccountManager;
 
-    private List<Intent> mBroadcastIntents = Lists.newArrayList();
+    private List<Intent> mBroadcastIntents = new ArrayList<>();
 
     public IsolatedContext(
             ContentResolver resolver, Context targetContext) {
@@ -67,7 +66,7 @@
     /** Returns the list of intents that were broadcast since the last call to this method. */
     public List<Intent> getAndClearBroadcastIntents() {
         List<Intent> intents = mBroadcastIntents;
-        mBroadcastIntents = Lists.newArrayList();
+        mBroadcastIntents = new ArrayList<>();
         return intents;
     }
 
diff --git a/test-runner/src/android/test/RenamingDelegatingContext.java b/test-runner/src/android/test/RenamingDelegatingContext.java
index 36786b0..fd33321 100644
--- a/test-runner/src/android/test/RenamingDelegatingContext.java
+++ b/test-runner/src/android/test/RenamingDelegatingContext.java
@@ -16,20 +16,24 @@
 
 package android.test;
 
-import com.google.android.collect.Sets;
-
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.ContentProvider;
 import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
-import android.os.FileUtils;
 import android.util.Log;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
+import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -48,8 +52,8 @@
     private File mCacheDir;
     private final Object mSync = new Object();
 
-    private Set<String> mDatabaseNames = Sets.newHashSet();
-    private Set<String> mFileNames = Sets.newHashSet();
+    private Set<String> mDatabaseNames = new HashSet<>();
+    private Set<String> mFileNames = new HashSet<>();
 
     public static <T extends ContentProvider> T providerWithRenamedContext(
             Class<T> contentProvider, Context c, String filePrefix)
@@ -237,10 +241,14 @@
                     Log.w("RenamingDelegatingContext", "Unable to create cache directory");
                     return null;
                 }
-                FileUtils.setPermissions(
-                        mCacheDir.getPath(),
-                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-                        -1, -1);
+                try {
+                    // Give the directory all possible permissions.
+                    Files.setPosixFilePermissions(mCacheDir.toPath(),
+                            EnumSet.allOf(PosixFilePermission.class));
+                } catch (IOException e) {
+                    Log.e("RenamingDelegatingContext",
+                            "Could not set permissions of test cacheDir", e);
+                }
             }
         }
         return mCacheDir;
diff --git a/test-runner/src/android/test/TestCaseUtil.java b/test-runner/src/android/test/TestCaseUtil.java
index c46d403..dc053a2 100644
--- a/test-runner/src/android/test/TestCaseUtil.java
+++ b/test-runner/src/android/test/TestCaseUtil.java
@@ -16,8 +16,7 @@
 
 package android.test;
 
-import com.google.android.collect.Lists;
-
+import java.util.ArrayList;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
@@ -44,7 +43,7 @@
     @SuppressWarnings("unchecked")
     public static List<String> getTestCaseNames(Test test, boolean flatten) {
         List<Test> tests = (List<Test>) getTests(test, flatten);
-        List<String> testCaseNames = Lists.newArrayList();
+        List<String> testCaseNames = new ArrayList<>();
         for (Test aTest : tests) {
             testCaseNames.add(getTestName(aTest));
         }
@@ -57,7 +56,7 @@
 
     private static List<? extends Test> getTests(Test test, boolean flatten,
             Set<Class<?>> seen) {
-        List<Test> testCases = Lists.newArrayList();
+        List<Test> testCases = new ArrayList<>();
         if (test != null) {
 
             Test workingTest = null;
diff --git a/test-runner/src/android/test/TestRunner.java b/test-runner/src/android/test/TestRunner.java
index beecc6f..ff045c3 100644
--- a/test-runner/src/android/test/TestRunner.java
+++ b/test-runner/src/android/test/TestRunner.java
@@ -32,7 +32,6 @@
 import junit.framework.TestListener;
 import junit.framework.Test;
 import junit.framework.TestResult;
-import com.google.android.collect.Lists;
 
 /**
  * Support class that actually runs a test. Android uses this class,
@@ -54,7 +53,7 @@
 
     private int mMode = REGRESSION;
 
-    private List<Listener> mListeners = Lists.newArrayList();
+    private List<Listener> mListeners = new ArrayList<>();
     private int mPassed;
     private int mFailed;
 
diff --git a/test-runner/src/android/test/mock/MockContentResolver.java b/test-runner/src/android/test/mock/MockContentResolver.java
index d8e0977..a70152c 100644
--- a/test-runner/src/android/test/mock/MockContentResolver.java
+++ b/test-runner/src/android/test/mock/MockContentResolver.java
@@ -23,8 +23,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 
-import com.google.android.collect.Maps;
-
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -67,7 +66,7 @@
      */
     public MockContentResolver(Context context) {
         super(context);
-        mProviders = Maps.newHashMap();
+        mProviders = new HashMap<>();
     }
 
     /**
diff --git a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
index 3b920cf..cf6936b 100644
--- a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
+++ b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
@@ -21,7 +21,6 @@
 import android.test.TestCaseUtil;
 import android.util.Log;
 import com.android.internal.util.Predicate;
-import com.google.android.collect.Lists;
 import static android.test.suitebuilder.TestGrouping.SORT_BY_FULLY_QUALIFIED_NAME;
 import static android.test.suitebuilder.TestPredicates.REJECT_SUPPRESSED;
 
@@ -69,7 +68,7 @@
     public TestSuiteBuilder(String name, ClassLoader classLoader) {
         this.suiteName = name;
         this.testGrouping.setClassLoader(classLoader);
-        this.testCases = Lists.newArrayList();
+        this.testCases = new ArrayList<>();
         addRequirements(REJECT_SUPPRESSED);
     }
 
diff --git a/test-runner/tests/src/android/test/AndroidTestRunnerTest.java b/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
index 0574704..6723548 100644
--- a/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
+++ b/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
@@ -19,8 +19,7 @@
 import android.test.mock.MockContext;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.google.android.collect.Lists;
-
+import java.util.ArrayList;
 import junit.framework.TestCase;
 import junit.framework.AssertionFailedError;
 import junit.framework.Test;
@@ -140,7 +139,7 @@
     public void testSetTestClassWithTestSuiteProvider() throws Exception {
         mAndroidTestRunner.setTestClassName(SampleTestSuiteProvider.class.getName(), null);
         List<TestCase> testCases = mAndroidTestRunner.getTestCases();
-        List<String> testNames = Lists.newArrayList();
+        List<String> testNames = new ArrayList<>();
         for (TestCase testCase : testCases) {
             testNames.add(testCase.getName());
         }
@@ -152,7 +151,7 @@
     public void testSetTestClassWithTestSuite() throws Exception {
         mAndroidTestRunner.setTestClassName(SampleTestSuite.class.getName(), null);
         List<TestCase> testCases = mAndroidTestRunner.getTestCases();
-        List<String> testNames = Lists.newArrayList();
+        List<String> testNames = new ArrayList<>();
         for (TestCase testCase : testCases) {
             testNames.add(testCase.getName());
         }
@@ -163,7 +162,7 @@
         String testMethodName = "testTwo";
         mAndroidTestRunner.setTestClassName(TwoTestTestCase.class.getName(), testMethodName);
         List<TestCase> testCases = mAndroidTestRunner.getTestCases();
-        List<String> testNames = Lists.newArrayList();
+        List<String> testNames = new ArrayList<>();
         for (TestCase testCase : testCases) {
             testNames.add(testCase.getName());
         }
@@ -255,7 +254,7 @@
     }
 
     private static class TestListenerStub implements TestListener {
-        List<String> testNames = Lists.newArrayList();
+        List<String> testNames = new ArrayList<>();
 
         public void addError(Test test, Throwable t) {
         }
diff --git a/tests/SurfaceComposition/Android.mk b/tests/SurfaceComposition/Android.mk
index 95f69f1..d97c3f4 100644
--- a/tests/SurfaceComposition/Android.mk
+++ b/tests/SurfaceComposition/Android.mk
@@ -27,6 +27,8 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
 LOCAL_PACKAGE_NAME := SurfaceComposition
 
 LOCAL_SDK_VERSION := current
diff --git a/tests/WindowAnimationJank/Android.mk b/tests/WindowAnimationJank/Android.mk
index 888ae64..f356afb 100644
--- a/tests/WindowAnimationJank/Android.mk
+++ b/tests/WindowAnimationJank/Android.mk
@@ -24,7 +24,11 @@
 
 LOCAL_PACKAGE_NAME := WindowAnimationJank
 
-LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator ub-janktesthelper
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ub-uiautomator \
+    ub-janktesthelper \
+    legacy-android-test \
+    junit
 
 LOCAL_SDK_VERSION := current
 
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index b984bbf..cc792cc 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -36,21 +36,49 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+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.PendingIntent;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
-
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.content.pm.ApplicationInfo;
+import android.os.Build.VERSION_CODES;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import org.junit.runner.RunWith;
+import org.junit.Before;
 import org.junit.Test;
-
-
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class ConnectivityManagerTest {
+
+    @Mock Context mCtx;
+    @Mock IConnectivityManager mService;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
     static NetworkCapabilities verifyNetworkCapabilities(
             int legacyType, int transportType, int... capabilities) {
         final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
@@ -173,4 +201,161 @@
         verifyUnrestrictedNetworkCapabilities(
                 ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET);
     }
+
+    @Test
+    public void testCallbackRelease() throws Exception {
+        ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+        NetworkRequest request = makeRequest(1);
+        NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+        Handler handler = new Handler(Looper.getMainLooper());
+        ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+        // register callback
+        when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+                .thenReturn(request);
+        manager.requestNetwork(request, callback, handler);
+
+        // callback triggers
+        captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_AVAILABLE));
+        verify(callback, timeout(500).times(1)).onAvailable(any());
+
+        // unregister callback
+        manager.unregisterNetworkCallback(callback);
+        verify(mService, times(1)).releaseNetworkRequest(request);
+
+        // callback does not trigger anymore.
+        captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_LOSING));
+        verify(callback, timeout(500).times(0)).onLosing(any(), anyInt());
+    }
+
+    @Test
+    public void testCallbackRecycling() throws Exception {
+        ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+        NetworkRequest req1 = makeRequest(1);
+        NetworkRequest req2 = makeRequest(2);
+        NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+        Handler handler = new Handler(Looper.getMainLooper());
+        ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+        // register callback
+        when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+                .thenReturn(req1);
+        manager.requestNetwork(req1, callback, handler);
+
+        // callback triggers
+        captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_AVAILABLE));
+        verify(callback, timeout(100).times(1)).onAvailable(any());
+
+        // unregister callback
+        manager.unregisterNetworkCallback(callback);
+        verify(mService, times(1)).releaseNetworkRequest(req1);
+
+        // callback does not trigger anymore.
+        captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_LOSING));
+        verify(callback, timeout(100).times(0)).onLosing(any(), anyInt());
+
+        // callback can be registered again
+        when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+                .thenReturn(req2);
+        manager.requestNetwork(req2, callback, handler);
+
+        // callback triggers
+        captor.getValue().send(makeMessage(req2, ConnectivityManager.CALLBACK_LOST));
+        verify(callback, timeout(100).times(1)).onLost(any());
+
+        // unregister callback
+        manager.unregisterNetworkCallback(callback);
+        verify(mService, times(1)).releaseNetworkRequest(req2);
+    }
+
+    // TODO: turn on this test when request  callback 1:1 mapping is enforced
+    //@Test
+    private void noDoubleCallbackRegistration() throws Exception {
+        ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+        NetworkRequest request = makeRequest(1);
+        NetworkCallback callback = new ConnectivityManager.NetworkCallback();
+        ApplicationInfo info = new ApplicationInfo();
+        // TODO: update version when starting to enforce 1:1 mapping
+        info.targetSdkVersion = VERSION_CODES.N_MR1 + 1;
+
+        when(mCtx.getApplicationInfo()).thenReturn(info);
+        when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt())).thenReturn(request);
+
+        Handler handler = new Handler(Looper.getMainLooper());
+        manager.requestNetwork(request, callback, handler);
+
+        // callback is already registered, reregistration should fail.
+        Class<IllegalArgumentException> wantException = IllegalArgumentException.class;
+        expectThrowable(() -> manager.requestNetwork(request, callback), wantException);
+
+        manager.unregisterNetworkCallback(callback);
+        verify(mService, times(1)).releaseNetworkRequest(request);
+
+        // unregistering the callback should make it registrable again.
+        manager.requestNetwork(request, callback);
+    }
+
+    @Test
+    public void testArgumentValidation() throws Exception {
+        ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+        NetworkRequest request = mock(NetworkRequest.class);
+        NetworkCallback callback = mock(NetworkCallback.class);
+        Handler handler = mock(Handler.class);
+        NetworkCallback nullCallback = null;
+        PendingIntent nullIntent = null;
+
+        mustFail(() -> { manager.requestNetwork(null, callback); });
+        mustFail(() -> { manager.requestNetwork(request, nullCallback); });
+        mustFail(() -> { manager.requestNetwork(request, callback, null); });
+        mustFail(() -> { manager.requestNetwork(request, callback, -1); });
+        mustFail(() -> { manager.requestNetwork(request, nullIntent); });
+
+        mustFail(() -> { manager.registerNetworkCallback(null, callback, handler); });
+        mustFail(() -> { manager.registerNetworkCallback(request, null, handler); });
+        mustFail(() -> { manager.registerNetworkCallback(request, callback, null); });
+        mustFail(() -> { manager.registerNetworkCallback(request, nullIntent); });
+
+        mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
+        mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
+
+        mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
+        mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
+        mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
+    }
+
+    static void mustFail(Runnable fn) {
+        try {
+            fn.run();
+            fail();
+        } catch (Exception expected) {
+        }
+    }
+
+    static Message makeMessage(NetworkRequest req, int messageType) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(NetworkRequest.class.getSimpleName(), req);
+        Message msg = Message.obtain();
+        msg.what = messageType;
+        msg.setData(bundle);
+        return msg;
+    }
+
+    static NetworkRequest makeRequest(int requestId) {
+        NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+        return new NetworkRequest(request.networkCapabilities, ConnectivityManager.TYPE_NONE,
+                requestId, NetworkRequest.Type.NONE);
+    }
+
+    static void expectThrowable(Runnable block, Class<? extends Throwable> throwableType) {
+        try {
+            block.run();
+        } catch (Throwable t) {
+            if (t.getClass().equals(throwableType)) {
+                return;
+            }
+            fail("expected exception of type " + throwableType + ", but was " + t.getClass());
+        }
+        fail("expected exception of type " + throwableType);
+    }
 }
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java
new file mode 100644
index 0000000..e3b06c8
--- /dev/null
+++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+import android.net.NetworkCapabilities;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkCapabilitiesTest {
+    @Test
+    public void testMaybeMarkCapabilitiesRestricted() {
+        // verify EIMS is restricted
+        assertEquals((1 << NET_CAPABILITY_EIMS) & RESTRICTED_CAPABILITIES,
+                (1 << NET_CAPABILITY_EIMS));
+
+        // verify CBS is also restricted
+        assertEquals((1 << NET_CAPABILITY_CBS) & RESTRICTED_CAPABILITIES,
+                (1 << NET_CAPABILITY_CBS));
+
+        // verify default is not restricted
+        assertEquals((1 << NET_CAPABILITY_INTERNET) & RESTRICTED_CAPABILITIES, 0);
+
+        // just to see
+        assertEquals(RESTRICTED_CAPABILITIES & UNRESTRICTED_CAPABILITIES, 0);
+
+        // check that internet does not get restricted
+        NetworkCapabilities netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // metered-ness shouldn't matter
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // add EIMS - bundled with unrestricted means it's unrestricted
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // just a restricted cap should be restricted regardless of meteredness
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // try 2 restricted caps
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_CBS);
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_CBS);
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+    }
+
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/tests/net/java/android/net/NetworkStatsHistoryTest.java
similarity index 99%
rename from core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
rename to tests/net/java/android/net/NetworkStatsHistoryTest.java
index 9a08f41..e7b91b5 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
+++ b/tests/net/java/android/net/NetworkStatsHistoryTest.java
@@ -38,7 +38,7 @@
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
-import com.android.frameworks.coretests.R;
+import com.android.frameworks.tests.net.R;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
similarity index 100%
rename from core/tests/coretests/src/android/net/NetworkStatsTest.java
rename to tests/net/java/android/net/NetworkStatsTest.java
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 91d6c68..d4896264 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -39,6 +39,8 @@
 
 import com.android.frameworks.tests.net.R;
 import com.android.internal.util.HexDump;
+import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.internal.util.BitUtils.put;
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -65,7 +67,7 @@
  * Tests for APF program generator and interpreter.
  *
  * Build, install and run with:
- *  runtest frameworks-services -c android.net.apf.ApfTest
+ *  runtest frameworks-net -c android.net.apf.ApfTest
  */
 public class ApfTest extends AndroidTestCase {
     private static final int TIMEOUT_MS = 500;
@@ -1235,15 +1237,6 @@
             byte[] apf_program);
 
     @SmallTest
-    public void testBytesToInt() {
-        assertEquals(0x00000000, ApfFilter.bytesToInt(IPV4_ANY_HOST_ADDR));
-        assertEquals(0xffffffff, ApfFilter.bytesToInt(IPV4_BROADCAST_ADDRESS));
-        assertEquals(0x0a000001, ApfFilter.bytesToInt(MOCK_IPV4_ADDR));
-        assertEquals(0x0a000002, ApfFilter.bytesToInt(ANOTHER_IPV4_ADDR));
-        assertEquals(0x0a001fff, ApfFilter.bytesToInt(MOCK_BROADCAST_IPV4_ADDR));
-        assertEquals(0xe0000001, ApfFilter.bytesToInt(MOCK_MULTICAST_IPV4_ADDR));
-    }
-
     public void testBroadcastAddress() throws Exception {
         assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
         assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
@@ -1257,7 +1250,7 @@
     }
 
     public void assertEqualsIp(String expected, int got) throws Exception {
-        int want = ApfFilter.bytesToInt(InetAddress.getByName(expected).getAddress());
+        int want = bytesToBEInt(InetAddress.getByName(expected).getAddress());
         assertEquals(want, got);
     }
 }
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
new file mode 100644
index 0000000..063cd5dc
--- /dev/null
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -0,0 +1,361 @@
+/*
+ * 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.nsd;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+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.os.HandlerThread;
+import android.os.Handler;
+import android.os.Looper;
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.os.Message;
+import android.os.Messenger;
+import com.android.internal.util.AsyncChannel;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdManagerTest {
+
+    static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
+    @Mock Context mContext;
+    @Mock INsdManager mService;
+    MockServiceHandler mServiceHandler;
+
+    long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mServiceHandler = spy(MockServiceHandler.create(mContext));
+        when(mService.getMessenger()).thenReturn(new Messenger(mServiceHandler));
+    }
+
+    @Test
+    public void testResolveService() {
+        NsdManager manager = makeManager();
+
+        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+        NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+        NsdManager.ResolveListener listener = mock(NsdManager.ResolveListener.class);
+
+        manager.resolveService(request, listener);
+        int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+        int err = 33;
+        sendResponse(NsdManager.RESOLVE_SERVICE_FAILED, err, key1, null);
+        verify(listener, timeout(mTimeoutMs).times(1)).onResolveFailed(request, err);
+
+        manager.resolveService(request, listener);
+        int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+        sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+    }
+
+    @Test
+    public void testParallelResolveService() {
+        NsdManager manager = makeManager();
+
+        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+        NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+
+        NsdManager.ResolveListener listener1 = mock(NsdManager.ResolveListener.class);
+        NsdManager.ResolveListener listener2 = mock(NsdManager.ResolveListener.class);
+
+        manager.resolveService(request, listener1);
+        int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+        manager.resolveService(request, listener2);
+        int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+        sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+        sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key1, reply);
+
+        verify(listener1, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+        verify(listener2, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+    }
+
+    @Test
+    public void testRegisterService() {
+        NsdManager manager = makeManager();
+
+        NsdServiceInfo request1 = new NsdServiceInfo("a_name", "a_type");
+        NsdServiceInfo request2 = new NsdServiceInfo("another_name", "another_type");
+        request1.setPort(2201);
+        request2.setPort(2202);
+        NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+        NsdManager.RegistrationListener listener2 = mock(NsdManager.RegistrationListener.class);
+
+        // Register two services
+        manager.registerService(request1, PROTOCOL, listener1);
+        int key1 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+        manager.registerService(request2, PROTOCOL, listener2);
+        int key2 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+        // First reques fails, second request succeeds
+        sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key2, request2);
+        verify(listener2, timeout(mTimeoutMs).times(1)).onServiceRegistered(request2);
+
+        int err = 1;
+        sendResponse(NsdManager.REGISTER_SERVICE_FAILED, err, key1, request1);
+        verify(listener1, timeout(mTimeoutMs).times(1)).onRegistrationFailed(request1, err);
+
+        // Client retries first request, it succeeds
+        manager.registerService(request1, PROTOCOL, listener1);
+        int key3 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+        sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key3, request1);
+        verify(listener1, timeout(mTimeoutMs).times(1)).onServiceRegistered(request1);
+
+        // First request is unregistered, it succeeds
+        manager.unregisterService(listener1);
+        int key3again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+        assertEquals(key3, key3again);
+
+        sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key3again, null);
+        verify(listener1, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request1);
+
+        // Second request is unregistered, it fails
+        manager.unregisterService(listener2);
+        int key2again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+        assertEquals(key2, key2again);
+
+        sendResponse(NsdManager.UNREGISTER_SERVICE_FAILED, err, key2again, null);
+        verify(listener2, timeout(mTimeoutMs).times(1)).onUnregistrationFailed(request2, err);
+
+        // TODO: do not unregister listener until service is unregistered
+        // Client retries unregistration of second request, it succeeds
+        //manager.unregisterService(listener2);
+        //int key2yetAgain = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+        //assertEquals(key2, key2yetAgain);
+
+        //sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key2yetAgain, null);
+        //verify(listener2, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request2);
+    }
+
+    @Test
+    public void testDiscoverService() {
+        NsdManager manager = makeManager();
+
+        NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type");
+        NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type");
+        NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type");
+
+        NsdManager.DiscoveryListener listener = mock(NsdManager.DiscoveryListener.class);
+
+        // Client registers for discovery, request fails
+        manager.discoverServices("a_type", PROTOCOL, listener);
+        int key1 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+        int err = 1;
+        sendResponse(NsdManager.DISCOVER_SERVICES_FAILED, err, key1, null);
+        verify(listener, timeout(mTimeoutMs).times(1)).onStartDiscoveryFailed("a_type", err);
+
+        // Client retries, request succeeds
+        manager.discoverServices("a_type", PROTOCOL, listener);
+        int key2 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+        sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key2, reply1);
+        verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+
+        // mdns notifies about services
+        sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply1);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply1);
+
+        sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply2);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply2);
+
+        sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply2);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply2);
+
+
+        // Client unregisters its listener
+        manager.stopServiceDiscovery(listener);
+        int key2again = verifyRequest(NsdManager.STOP_DISCOVERY);
+        assertEquals(key2, key2again);
+
+        // TODO: unregister listener immediately and stop notifying it about services
+        // Notifications are still passed to the client's listener
+        sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply1);
+        verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply1);
+
+        // Client is notified of complete unregistration
+        sendResponse(NsdManager.STOP_DISCOVERY_SUCCEEDED, 0, key2again, "a_type");
+        verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStopped("a_type");
+
+        // Notifications are not passed to the client anymore
+        sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply3);
+        verify(listener, timeout(mTimeoutMs).times(0)).onServiceLost(reply3);
+
+
+        // Client registers for service discovery
+        reset(listener);
+        manager.discoverServices("a_type", PROTOCOL, listener);
+        int key3 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+        sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key3, reply1);
+        verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+        // Client unregisters immediately, it fails
+        manager.stopServiceDiscovery(listener);
+        int key3again = verifyRequest(NsdManager.STOP_DISCOVERY);
+        assertEquals(key3, key3again);
+
+        err = 2;
+        sendResponse(NsdManager.STOP_DISCOVERY_FAILED, err, key3again, "a_type");
+        verify(listener, timeout(mTimeoutMs).times(1)).onStopDiscoveryFailed("a_type", err);
+
+        // New notifications are not passed to the client anymore
+        sendResponse(NsdManager.SERVICE_FOUND, 0, key3, reply1);
+        verify(listener, timeout(mTimeoutMs).times(0)).onServiceFound(reply1);
+    }
+
+    @Test
+    public void testInvalidCalls() {
+        NsdManager manager = new NsdManager(mContext, mService);
+
+        NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+        NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+        NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+
+        NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
+        NsdServiceInfo validService = new NsdServiceInfo("a_name", "a_type");
+        validService.setPort(2222);
+
+        // Service registration
+        //  - invalid arguments
+        mustFail(() -> { manager.unregisterService(null); });
+        mustFail(() -> { manager.registerService(null, -1, null); });
+        mustFail(() -> { manager.registerService(null, PROTOCOL, listener1); });
+        mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); });
+        mustFail(() -> { manager.registerService(validService, -1, listener1); });
+        mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
+        manager.registerService(validService, PROTOCOL, listener1);
+        //  - listener already registered
+        mustFail(() -> { manager.registerService(validService, PROTOCOL, listener1); });
+        manager.unregisterService(listener1);
+        // TODO: make listener immediately reusable
+        //mustFail(() -> { manager.unregisterService(listener1); });
+        //manager.registerService(validService, PROTOCOL, listener1);
+
+        // Discover service
+        //  - invalid arguments
+        mustFail(() -> { manager.stopServiceDiscovery(null); });
+        mustFail(() -> { manager.discoverServices(null, -1, null); });
+        mustFail(() -> { manager.discoverServices(null, PROTOCOL, listener2); });
+        mustFail(() -> { manager.discoverServices("a_service", -1, listener2); });
+        mustFail(() -> { manager.discoverServices("a_service", PROTOCOL, null); });
+        manager.discoverServices("a_service", PROTOCOL, listener2);
+        //  - listener already registered
+        mustFail(() -> { manager.discoverServices("another_service", PROTOCOL, listener2); });
+        manager.stopServiceDiscovery(listener2);
+        // TODO: make listener immediately reusable
+        //mustFail(() -> { manager.stopServiceDiscovery(listener2); });
+        //manager.discoverServices("another_service", PROTOCOL, listener2);
+
+        // Resolver service
+        //  - invalid arguments
+        mustFail(() -> { manager.resolveService(null, null); });
+        mustFail(() -> { manager.resolveService(null, listener3); });
+        mustFail(() -> { manager.resolveService(invalidService, listener3); });
+        mustFail(() -> { manager.resolveService(validService, null); });
+        manager.resolveService(validService, listener3);
+        //  - listener already registered:w
+        mustFail(() -> { manager.resolveService(validService, listener3); });
+    }
+
+    public void mustFail(Runnable fn) {
+        try {
+            fn.run();
+            fail();
+        } catch (Exception expected) {
+        }
+    }
+
+    NsdManager makeManager() {
+        NsdManager manager = new NsdManager(mContext, mService);
+        // Acknowledge first two messages connecting the AsyncChannel.
+        verify(mServiceHandler, timeout(mTimeoutMs).times(2)).handleMessage(any());
+        reset(mServiceHandler);
+        assertNotNull(mServiceHandler.chan);
+        return manager;
+    }
+
+    int verifyRequest(int expectedMessageType) {
+        verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
+        reset(mServiceHandler);
+        Message received = mServiceHandler.lastMessage;
+        assertEquals(NsdManager.nameOf(expectedMessageType), NsdManager.nameOf(received.what));
+        return received.arg2;
+    }
+
+    void sendResponse(int replyType, int arg, int key, Object obj) {
+        mServiceHandler.chan.sendMessage(replyType, arg, key, obj);
+    }
+
+    // Implements the server side of AsyncChannel connection protocol
+    public static class MockServiceHandler extends Handler {
+        public Context mContext;
+        public AsyncChannel chan;
+        public Message lastMessage;
+
+        MockServiceHandler(Looper looper, Context context) {
+            super(looper);
+            mContext = context;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            lastMessage = obtainMessage();
+            lastMessage.copyFrom(msg);
+            if (msg.what == AsyncChannel.CMD_CHANNEL_FULL_CONNECTION) {
+                chan = new AsyncChannel();
+                chan.connect(mContext, this, msg.replyTo);
+                chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
+            }
+
+        }
+
+        public static MockServiceHandler create(Context context) {
+            HandlerThread t = new HandlerThread("mock-service-handler");
+            t.start();
+            return new MockServiceHandler(t.getLooper(), context);
+        }
+    }
+}
diff --git a/tests/net/java/android/net/nsd/NsdServiceTest.java b/tests/net/java/android/net/nsd/NsdServiceTest.java
new file mode 100644
index 0000000..68cb251
--- /dev/null
+++ b/tests/net/java/android/net/nsd/NsdServiceTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.any;
+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.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import com.android.server.NsdService.DaemonConnection;
+import com.android.server.NsdService.DaemonConnectionSupplier;
+import com.android.server.NsdService.NativeCallbackReceiver;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+// TODOs:
+//  - test client can send requests and receive replies
+//  - test NSD_ON ENABLE/DISABLED listening
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdServiceTest {
+
+    static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
+    long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+
+    @Mock Context mContext;
+    @Mock ContentResolver mResolver;
+    @Mock NsdService.NsdSettings mSettings;
+    @Mock DaemonConnection mDaemon;
+    NativeCallbackReceiver mDaemonCallback;
+    HandlerThread mThread;
+    TestHandler mHandler;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mThread = new HandlerThread("mock-service-handler");
+        mThread.start();
+        mHandler = new TestHandler(mThread.getLooper());
+        when(mContext.getContentResolver()).thenReturn(mResolver);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mThread.quit();
+    }
+
+    @Test
+    public void testClientsCanConnectAndDisconnect() {
+        when(mSettings.isEnabled()).thenReturn(true);
+
+        NsdService service = makeService();
+
+        NsdManager client1 = connectClient(service);
+        verify(mDaemon, timeout(100).times(1)).start();
+
+        NsdManager client2 = connectClient(service);
+
+        client1.disconnect();
+        client2.disconnect();
+
+        verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+    }
+
+    @Test
+    public void testClientRequestsAreGCedAtDisconnection() {
+        when(mSettings.isEnabled()).thenReturn(true);
+        when(mDaemon.execute(any())).thenReturn(true);
+
+        NsdService service = makeService();
+        NsdManager client = connectClient(service);
+
+        verify(mDaemon, timeout(100).times(1)).start();
+
+        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+        request.setPort(2201);
+
+        // Client registration request
+        NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+        client.registerService(request, PROTOCOL, listener1);
+        verifyDaemonCommand("register 2 a_name a_type 2201");
+
+        // Client discovery request
+        NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+        client.discoverServices("a_type", PROTOCOL, listener2);
+        verifyDaemonCommand("discover 3 a_type");
+
+        // Client resolve request
+        NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+        client.resolveService(request, listener3);
+        verifyDaemonCommand("resolve 4 a_name a_type local.");
+
+        // Client disconnects
+        client.disconnect();
+        verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+
+        // checks that request are cleaned
+        verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4");
+    }
+
+    NsdService makeService() {
+        DaemonConnectionSupplier supplier = (callback) -> {
+            mDaemonCallback = callback;
+            return mDaemon;
+        };
+        NsdService service = new NsdService(mContext, mSettings, mHandler, supplier);
+        verify(mDaemon, never()).execute(any(String.class));
+        return service;
+    }
+
+    NsdManager connectClient(NsdService service) {
+        return new NsdManager(mContext, service);
+    }
+
+    void verifyDaemonCommands(String... wants) {
+        verifyDaemonCommand(String.join(" ", wants), wants.length);
+    }
+
+    void verifyDaemonCommand(String want) {
+        verifyDaemonCommand(want, 1);
+    }
+
+    void verifyDaemonCommand(String want, int n) {
+        ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
+        verify(mDaemon, timeout(mTimeoutMs).times(n)).execute(argumentsCaptor.capture());
+        String got = "";
+        for (Object o : argumentsCaptor.getAllValues()) {
+            got += o + " ";
+        }
+        assertEquals(want, got.trim());
+        // rearm deamon for next command verification
+        reset(mDaemon);
+        when(mDaemon.execute(any())).thenReturn(true);
+    }
+
+    public static class TestHandler extends Handler {
+        public Message lastMessage;
+
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            lastMessage = obtainMessage();
+            lastMessage.copyFrom(msg);
+        }
+    }
+}
diff --git a/tests/net/java/android/net/util/SharedLogTest.java b/tests/net/java/android/net/util/SharedLogTest.java
new file mode 100644
index 0000000..7fd7a63
--- /dev/null
+++ b/tests/net/java/android/net/util/SharedLogTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Vector;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SharedLogTest {
+    private static final String TIMESTAMP_PATTERN =
+            "^[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]";
+    private static final String TIMESTAMP = "mm-dd HH:MM:SS.xxx";
+
+    @Test
+    public void testBasicOperation() {
+        final SharedLog logTop = new SharedLog("top");
+        logTop.mark("first post!");
+
+        final SharedLog logLevel2a = logTop.forSubComponent("twoA");
+        final SharedLog logLevel2b = logTop.forSubComponent("twoB");
+        logLevel2b.e("2b or not 2b");
+        logLevel2a.w("second post?");
+
+        final SharedLog logLevel3 = logLevel2a.forSubComponent("three");
+        logTop.log("still logging");
+        logLevel3.log("3 >> 2");
+        logLevel2a.mark("ok: last post");
+
+        final String[] expected = {
+            TIMESTAMP + " - MARK first post!",
+            TIMESTAMP + " - [twoB] ERROR 2b or not 2b",
+            TIMESTAMP + " - [twoA] WARN second post?",
+            TIMESTAMP + " - still logging",
+            TIMESTAMP + " - [twoA.three] 3 >> 2",
+            TIMESTAMP + " - [twoA] MARK ok: last post",
+        };
+        // Verify the logs are all there and in the correct order.
+        verifyLogLines(expected, logTop);
+
+        // In fact, because they all share the same underlying LocalLog,
+        // every subcomponent SharedLog's dump() is identical.
+        verifyLogLines(expected, logLevel2a);
+        verifyLogLines(expected, logLevel2b);
+        verifyLogLines(expected, logLevel3);
+    }
+
+    private static void verifyLogLines(String[] expected, SharedLog log) {
+        final ByteArrayOutputStream ostream = new ByteArrayOutputStream();
+        final PrintWriter pw = new PrintWriter(ostream, true);
+        log.dump(null, pw, null);
+
+        final String dumpOutput = ostream.toString();
+        assertTrue(dumpOutput != null);
+        assertTrue(!"".equals(dumpOutput));
+
+        final String[] lines = dumpOutput.split("\n");
+        assertEquals(expected.length, lines.length);
+
+        for (int i = 0; i < lines.length; i++) {
+            // Fix up the timestamps.
+            lines[i] = lines[i].replaceAll(TIMESTAMP_PATTERN, TIMESTAMP);
+        }
+
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], lines[i]);
+        }
+    }
+}
diff --git a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
new file mode 100644
index 0000000..a423c2a
--- /dev/null
+++ b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net;
+
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+
+import android.content.res.Resources;
+import android.net.NetworkStats;
+import android.net.TrafficStats;
+import android.support.test.filters.SmallTest;
+import android.test.AndroidTestCase;
+
+import com.android.frameworks.tests.net.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkStatsFactory}.
+ */
+@SmallTest
+public class NetworkStatsFactoryTest extends AndroidTestCase {
+    private File mTestProc;
+    private NetworkStatsFactory mFactory;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mTestProc = new File(getContext().getFilesDir(), "proc");
+        if (mTestProc.exists()) {
+            IoUtils.deleteContents(mTestProc);
+        }
+
+        mFactory = new NetworkStatsFactory(mTestProc);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mFactory = null;
+
+        if (mTestProc.exists()) {
+            IoUtils.deleteContents(mTestProc);
+        }
+
+        super.tearDown();
+    }
+
+    public void testNetworkStatsDetail() throws Exception {
+        final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
+
+        assertEquals(70, stats.size());
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
+        assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
+        assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
+        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
+        assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
+    }
+
+    public void testKernelTags() throws Exception {
+        assertEquals(0, kernelToTag("0x0000000000000000"));
+        assertEquals(0x32, kernelToTag("0x0000003200000000"));
+        assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
+        assertEquals(0, kernelToTag("0x0000000000000000"));
+        assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
+
+        assertEquals(0, kernelToTag("0x0"));
+        assertEquals(0, kernelToTag("0xf00d"));
+        assertEquals(1, kernelToTag("0x100000000"));
+        assertEquals(14438007, kernelToTag("0xdc4e7700000000"));
+        assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000"));
+    }
+
+    public void testNetworkStatsWithSet() throws Exception {
+        final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
+        assertEquals(70, stats.size());
+        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L,
+                676L);
+        assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
+    }
+
+    public void testNetworkStatsSingle() throws Exception {
+        stageFile(R.raw.xt_qtaguid_iface_typical, file("net/xt_qtaguid/iface_stat_all"));
+
+        final NetworkStats stats = mFactory.readNetworkStatsSummaryDev();
+        assertEquals(6, stats.size());
+        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L);
+        assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L);
+        assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
+    }
+
+    public void testNetworkStatsXt() throws Exception {
+        stageFile(R.raw.xt_qtaguid_iface_fmt_typical, file("net/xt_qtaguid/iface_stat_fmt"));
+
+        final NetworkStats stats = mFactory.readNetworkStatsSummaryXt();
+        assertEquals(3, stats.size());
+        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L);
+        assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L,
+                2468L);
+        assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
+    }
+
+    public void testDoubleClatAccounting() throws Exception {
+        NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0");
+
+        // xt_qtaguid_with_clat_simple is a synthetic file that simulates
+        //  - 213 received 464xlat packets of size 200 bytes
+        //  - 41 sent 464xlat packets of size 100 bytes
+        //  - no other traffic on base interface for root uid.
+        NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_simple);
+        assertEquals(4, stats.size());
+
+        assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 46860L, 4920L);
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L);
+
+        stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat);
+        assertEquals(42, stats.size());
+
+        assertStatsEntry(stats, "v4-wlan0", 0, SET_DEFAULT, 0x0, 356L, 276L);
+        assertStatsEntry(stats, "v4-wlan0", 1000, SET_DEFAULT, 0x0, 30812L, 2310L);
+        assertStatsEntry(stats, "v4-wlan0", 10102, SET_DEFAULT, 0x0, 10022L, 3330L);
+        assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 9532772L, 254112L);
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 15229L, 5766L);
+        assertStatsEntry(stats, "wlan0", 1000, SET_DEFAULT, 0x0, 6126L, 2013L);
+        assertStatsEntry(stats, "wlan0", 10013, SET_DEFAULT, 0x0, 0L, 144L);
+        assertStatsEntry(stats, "wlan0", 10018, SET_DEFAULT, 0x0, 5980263L, 167667L);
+        assertStatsEntry(stats, "wlan0", 10060, SET_DEFAULT, 0x0, 134356L, 8705L);
+        assertStatsEntry(stats, "wlan0", 10079, SET_DEFAULT, 0x0, 10926L, 1507L);
+        assertStatsEntry(stats, "wlan0", 10102, SET_DEFAULT, 0x0, 25038L, 8245L);
+        assertStatsEntry(stats, "wlan0", 10103, SET_DEFAULT, 0x0, 0L, 192L);
+        assertStatsEntry(stats, "dummy0", 0, SET_DEFAULT, 0x0, 0L, 168L);
+        assertStatsEntry(stats, "lo", 0, SET_DEFAULT, 0x0, 1288L, 1288L);
+
+        NetworkStatsFactory.noteStackedIface("v4-wlan0", null);
+    }
+
+    public void testDoubleClatAccounting100MBDownload() throws Exception {
+        // Downloading 100mb from an ipv4 only destination in a foreground activity
+
+        long appRxBytesBefore = 328684029L;
+        long appRxBytesAfter = 439237478L;
+        assertEquals("App traffic should be ~100MB", 110553449, appRxBytesAfter - appRxBytesBefore);
+
+        long rootRxBytesBefore = 1394011L;
+        long rootRxBytesAfter = 1398634L;
+        assertEquals("UID 0 traffic should be ~0", 4623, rootRxBytesAfter - rootRxBytesBefore);
+
+        NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0");
+        NetworkStats stats;
+
+        // Stats snapshot before the download
+        stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_before);
+        assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesBefore, 5199872L);
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesBefore, 647888L);
+
+        // Stats snapshot after the download
+        stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_after);
+        assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesAfter, 7867488L);
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesAfter, 647587L);
+
+        NetworkStatsFactory.noteStackedIface("v4-wlan0", null);
+    }
+
+    /**
+     * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+     * testing purposes.
+     */
+    private void stageFile(int rawId, File file) throws Exception {
+        new File(file.getParent()).mkdirs();
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            in = getContext().getResources().openRawResource(rawId);
+            out = new FileOutputStream(file);
+            Streams.copy(in, out);
+        } finally {
+            IoUtils.closeQuietly(in);
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    private void stageLong(long value, File file) throws Exception {
+        new File(file.getParent()).mkdirs();
+        FileWriter out = null;
+        try {
+            out = new FileWriter(file);
+            out.write(Long.toString(value));
+        } finally {
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    private File file(String path) throws Exception {
+        return new File(mTestProc, path);
+    }
+
+    private NetworkStats parseDetailedStats(int resourceId) throws Exception {
+        stageFile(resourceId, file("net/xt_qtaguid/stats"));
+        return mFactory.readNetworkStatsDetail();
+    }
+
+    private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+            int tag, long rxBytes, long txBytes) {
+        final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO);
+        if (i < 0) {
+            fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)",
+                    iface, uid, set, tag));
+        }
+        final NetworkStats.Entry entry = stats.getValues(i, null);
+        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+    }
+
+    private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+        final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO);
+        if (i < 0) {
+            fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)",
+                    iface, uid, set, tag));
+        }
+        final NetworkStats.Entry entry = stats.getValues(i, null);
+        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+        assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+        assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+    }
+
+}
diff --git a/tests/net/java/com/android/internal/util/BitUtilsTest.java b/tests/net/java/com/android/internal/util/BitUtilsTest.java
new file mode 100644
index 0000000..0ad8a21
--- /dev/null
+++ b/tests/net/java/com/android/internal/util/BitUtilsTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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 android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import java.nio.ByteBuffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static com.android.internal.util.BitUtils.*;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BitUtilsTest {
+
+    @Test
+    public void testUnsignedByteWideningConversions() {
+        byte b0 = 0;
+        byte b1 = 1;
+        byte bm1 = -1;
+        assertEquals(0, uint8(b0));
+        assertEquals(1, uint8(b1));
+        assertEquals(127, uint8(Byte.MAX_VALUE));
+        assertEquals(128, uint8(Byte.MIN_VALUE));
+        assertEquals(255, uint8(bm1));
+        assertEquals(255, uint8((byte)255));
+    }
+
+    @Test
+    public void testUnsignedShortWideningConversions() {
+        short s0 = 0;
+        short s1 = 1;
+        short sm1 = -1;
+        assertEquals(0, uint16(s0));
+        assertEquals(1, uint16(s1));
+        assertEquals(32767, uint16(Short.MAX_VALUE));
+        assertEquals(32768, uint16(Short.MIN_VALUE));
+        assertEquals(65535, uint16(sm1));
+        assertEquals(65535, uint16((short)65535));
+    }
+
+    @Test
+    public void testUnsignedIntWideningConversions() {
+        assertEquals(0, uint32(0));
+        assertEquals(1, uint32(1));
+        assertEquals(2147483647L, uint32(Integer.MAX_VALUE));
+        assertEquals(2147483648L, uint32(Integer.MIN_VALUE));
+        assertEquals(4294967295L, uint32(-1));
+        assertEquals(4294967295L, uint32((int)4294967295L));
+    }
+
+    @Test
+    public void testBytesToInt() {
+        assertEquals(0x00000000, bytesToBEInt(bytes(0, 0, 0, 0)));
+        assertEquals(0xffffffff, bytesToBEInt(bytes(255, 255, 255, 255)));
+        assertEquals(0x0a000001, bytesToBEInt(bytes(10, 0, 0, 1)));
+        assertEquals(0x0a000002, bytesToBEInt(bytes(10, 0, 0, 2)));
+        assertEquals(0x0a001fff, bytesToBEInt(bytes(10, 0, 31, 255)));
+        assertEquals(0xe0000001, bytesToBEInt(bytes(224, 0, 0, 1)));
+
+        assertEquals(0x00000000, bytesToLEInt(bytes(0, 0, 0, 0)));
+        assertEquals(0x01020304, bytesToLEInt(bytes(4, 3, 2, 1)));
+        assertEquals(0xffff0000, bytesToLEInt(bytes(0, 0, 255, 255)));
+    }
+
+    @Test
+    public void testUnsignedGetters() {
+      ByteBuffer b = ByteBuffer.allocate(4);
+      b.putInt(0xffff);
+
+      assertEquals(0x0, getUint8(b, 0));
+      assertEquals(0x0, getUint8(b, 1));
+      assertEquals(0xff, getUint8(b, 2));
+      assertEquals(0xff, getUint8(b, 3));
+
+      assertEquals(0x0, getUint16(b, 0));
+      assertEquals(0xffff, getUint16(b, 2));
+
+      b.rewind();
+      b.putInt(0xffffffff);
+      assertEquals(0xffffffffL, getUint32(b, 0));
+    }
+
+    static byte[] bytes(int b1, int b2, int b3, int b4) {
+        return new byte[] {b(b1), b(b2), b(b3), b(b4)};
+    }
+
+    static byte b(int i) {
+        return (byte) i;
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2f5c97d..00b0f98 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -39,15 +39,18 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
+import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivityManager.PacketKeepalive;
 import android.net.ConnectivityManager.PacketKeepaliveCallback;
+import android.net.ConnectivityManager.TooManyRequestsException;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.MatchAllNetworkSpecifier;
 import android.net.Network;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
@@ -57,7 +60,9 @@
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkMisc;
 import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.RouteInfo;
+import android.net.StringNetworkSpecifier;
 import android.net.metrics.IpConnectivityLog;
 import android.net.util.MultinetworkPolicyTracker;
 import android.os.ConditionVariable;
@@ -70,12 +75,16 @@
 import android.os.MessageQueue;
 import android.os.Messenger;
 import android.os.MessageQueue.IdleHandler;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.LogPrinter;
 
@@ -106,7 +115,7 @@
  * Tests for {@link ConnectivityService}.
  *
  * Build, install and run with:
- *  runtest frameworks-services -c com.android.server.ConnectivityServiceTest
+ *  runtest frameworks-net -c com.android.server.ConnectivityServiceTest
  */
 public class ConnectivityServiceTest extends AndroidTestCase {
     private static final String TAG = "ConnectivityServiceTest";
@@ -114,7 +123,7 @@
     private static final int TIMEOUT_MS = 500;
     private static final int TEST_LINGER_DELAY_MS = 120;
 
-    private BroadcastInterceptingContext mServiceContext;
+    private MockContext mServiceContext;
     private WrappedConnectivityService mService;
     private WrappedConnectivityManager mCm;
     private MockNetworkAgent mWiFiNetworkAgent;
@@ -145,6 +154,7 @@
         private final MockContentResolver mContentResolver;
 
         @Spy private Resources mResources;
+        private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>();
 
         MockContext(Context base) {
             super(base);
@@ -162,6 +172,27 @@
         }
 
         @Override
+        public void startActivityAsUser(Intent intent, UserHandle handle) {
+            mStartedActivities.offer(intent);
+        }
+
+        public Intent expectStartActivityIntent(int timeoutMs) {
+            Intent intent = null;
+            try {
+                intent = mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {}
+            assertNotNull("Did not receive sign-in intent after " + timeoutMs + "ms", intent);
+            return intent;
+        }
+
+        public void expectNoStartActivityIntent(int timeoutMs) {
+            try {
+                assertNull("Received unexpected Intent to start activity",
+                        mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {}
+        }
+
+        @Override
         public Object getSystemService(String name) {
             if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
             if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
@@ -192,13 +223,32 @@
         }
     }
 
+    public void waitForIdle(int timeoutMs) {
+        waitForIdleHandler(mService.mHandlerThread, timeoutMs);
+        waitForIdle(mCellNetworkAgent, timeoutMs);
+        waitForIdle(mWiFiNetworkAgent, timeoutMs);
+        waitForIdle(mEthernetNetworkAgent, timeoutMs);
+        waitForIdleHandler(mService.mHandlerThread, timeoutMs);
+    }
+
+    public void waitForIdle(MockNetworkAgent agent, int timeoutMs) {
+        if (agent == null) {
+            return;
+        }
+        waitForIdleHandler(agent.mHandlerThread, timeoutMs);
+    }
+
+    private void waitForIdle() {
+        waitForIdle(TIMEOUT_MS);
+    }
+
     @SmallTest
     public void testWaitForIdle() {
         final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
 
         // Tests that waitForIdle returns immediately if the service is already idle.
         for (int i = 0; i < attempts; i++) {
-            mService.waitForIdle();
+            waitForIdle();
         }
 
         // Bring up a network that we can use to send messages to ConnectivityService.
@@ -212,7 +262,7 @@
         // Tests that calling waitForIdle waits for messages to be processed.
         for (int i = 0; i < attempts; i++) {
             mWiFiNetworkAgent.setSignalStrength(i);
-            mService.waitForIdle();
+            waitForIdle();
             assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength());
         }
     }
@@ -313,23 +363,19 @@
             };
             // Waits for the NetworkAgent to be registered, which includes the creation of the
             // NetworkMonitor.
-            mService.waitForIdle();
+            waitForIdle();
             mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
         }
 
-        public void waitForIdle(int timeoutMs) {
-            waitForIdleHandler(mHandlerThread, timeoutMs);
-        }
-
-        public void waitForIdle() {
-            waitForIdle(TIMEOUT_MS);
-        }
-
         public void adjustScore(int change) {
             mScore += change;
             mNetworkAgent.sendNetworkScore(mScore);
         }
 
+        public void explicitlySelected(boolean acceptUnvalidated) {
+            mNetworkAgent.explicitlySelected(acceptUnvalidated);
+        }
+
         public void addCapability(int capability) {
             mNetworkCapabilities.addCapability(capability);
             mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
@@ -345,8 +391,8 @@
             mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
         }
 
-        public void setNetworkSpecifier(String specifier) {
-            mNetworkCapabilities.setNetworkSpecifier(specifier);
+        public void setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+            mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
             mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
         }
 
@@ -762,17 +808,27 @@
 
         // Ensure that the default setting for Captive Portals is used for most tests
         setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+        setMobileDataAlwaysOn(false);
     }
 
     public void tearDown() throws Exception {
         setMobileDataAlwaysOn(false);
-        if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); }
-        if (mWiFiNetworkAgent != null) { mWiFiNetworkAgent.disconnect(); }
-        mCellNetworkAgent = mWiFiNetworkAgent = null;
+        if (mCellNetworkAgent != null) {
+            mCellNetworkAgent.disconnect();
+            mCellNetworkAgent = null;
+        }
+        if (mWiFiNetworkAgent != null) {
+            mWiFiNetworkAgent.disconnect();
+            mWiFiNetworkAgent = null;
+        }
+        if (mEthernetNetworkAgent != null) {
+            mEthernetNetworkAgent.disconnect();
+            mEthernetNetworkAgent = null;
+        }
         super.tearDown();
     }
 
-    private int transportToLegacyType(int transport) {
+    private static int transportToLegacyType(int transport) {
         switch (transport) {
             case TRANSPORT_ETHERNET:
                 return TYPE_ETHERNET;
@@ -804,7 +860,8 @@
         }
         // Test getNetworkInfo(Network)
         assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork()));
-        assertEquals(transportToLegacyType(transport), mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
+        assertEquals(transportToLegacyType(transport),
+                mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
         // Test getNetworkCapabilities(Network)
         assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork()));
         assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport));
@@ -875,7 +932,7 @@
                 mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
         // Test cellular linger timeout.
         waitFor(mCellNetworkAgent.getDisconnectedCV());
-        mService.waitForIdle();
+        waitForIdle();
         assertEquals(1, mCm.getAllNetworks().length);
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertEquals(1, mCm.getAllNetworks().length);
@@ -898,11 +955,11 @@
         // Test bringing up unvalidated cellular
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        mService.waitForIdle();
+        waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test cellular disconnect.
         mCellNetworkAgent.disconnect();
-        mService.waitForIdle();
+        waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up validated cellular
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1254,7 +1311,7 @@
         }
 
         void assertNoCallback() {
-            mService.waitForIdle();
+            waitForIdle();
             CallbackInfo c = mCallbacks.peek();
             assertNull("Unexpected callback: " + c, c);
         }
@@ -1295,7 +1352,7 @@
 
         // This should not trigger spurious onAvailable() callbacks, b/21762680.
         mCellNetworkAgent.adjustScore(-1);
-        mService.waitForIdle();
+        waitForIdle();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
@@ -1333,7 +1390,7 @@
 
         // This should not trigger spurious onAvailable() callbacks, b/21762680.
         mCellNetworkAgent.adjustScore(-1);
-        mService.waitForIdle();
+        waitForIdle();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
@@ -1445,7 +1502,7 @@
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
 
         mCm.unregisterNetworkCallback(callback);
-        mService.waitForIdle();
+        waitForIdle();
 
         // Check that a network is only lingered or torn down if it would not satisfy a request even
         // if it validated.
@@ -1560,13 +1617,104 @@
         final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
         callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
 
+        // Register a TRACK_DEFAULT request and check that it does not affect lingering.
+        TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
+        mCm.registerDefaultNetworkCallback(trackDefaultCallback);
+        trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent.connect(true);
+        callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+        callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
+        trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+
+        // Let linger run its course.
+        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
+
         // Clean up.
-        mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        mEthernetNetworkAgent.disconnect();
+        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        trackDefaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(defaultCallback);
+        mCm.unregisterNetworkCallback(trackDefaultCallback);
+    }
+
+    @SmallTest
+    public void testExplicitlySelected() {
+        NetworkRequest request = new NetworkRequest.Builder()
+                .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(request, callback);
+
+        // Bring up validated cell.
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+
+        // Bring up unvalidated wifi with explicitlySelected=true.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(false);
+        mWiFiNetworkAgent.connect(false);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+
+        // Cell Remains the default.
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        // Lower wifi's score to below than cell, and check that it doesn't disconnect because
+        // it's explicitly selected.
+        mWiFiNetworkAgent.adjustScore(-40);
+        mWiFiNetworkAgent.adjustScore(40);
+        callback.assertNoCallback();
+
+        // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to
+        // wifi even though it's unvalidated.
+        mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false);
+        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        // Disconnect wifi, and then reconnect, again with explicitlySelected=true.
+        mWiFiNetworkAgent.disconnect();
+        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(false);
+        mWiFiNetworkAgent.connect(false);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+
+        // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
+        // network to disconnect.
+        mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
+        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // Reconnect, again with explicitlySelected=true, but this time validate.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.explicitlySelected(false);
+        mWiFiNetworkAgent.connect(true);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        // BUG: the network will no longer linger, even though it's validated and outscored.
+        // TODO: fix this.
+        mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent.connect(true);
+        callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+        assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        callback.assertNoCallback();
+
+        // Clean up.
+        mWiFiNetworkAgent.disconnect();
+        mCellNetworkAgent.disconnect();
+        mEthernetNetworkAgent.disconnect();
+
+        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
     }
 
     private void tryNetworkFactoryRequests(int capability) throws Exception {
@@ -1715,26 +1863,30 @@
         ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV();
         mCellNetworkAgent.connectWithoutInternet();
         waitFor(cv);
-        mService.waitForIdle();
+        waitForIdle();
         assertEquals(0, mCm.getAllNetworks().length);
         verifyNoNetwork();
+
         // Test bringing up validated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent.connect(true);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
+
         // Register MMS NetworkRequest
         NetworkRequest.Builder builder = new NetworkRequest.Builder();
         builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.requestNetwork(builder.build(), networkCallback);
+
         // Test bringing up unvalidated cellular with MMS
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mCellNetworkAgent.connectWithoutInternet();
         networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
         verifyActiveNetwork(TRANSPORT_WIFI);
+
         // Test releasing NetworkRequest disconnects cellular with MMS
         cv = mCellNetworkAgent.getDisconnectedCV();
         mCm.unregisterNetworkCallback(networkCallback);
@@ -1750,17 +1902,20 @@
         mCellNetworkAgent.connect(false);
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
+
         // Register MMS NetworkRequest
         NetworkRequest.Builder builder = new NetworkRequest.Builder();
         builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.requestNetwork(builder.build(), networkCallback);
+
         // Test bringing up MMS cellular network
         MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mmsNetworkAgent.connectWithoutInternet();
         networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
+
         // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
         cv = mmsNetworkAgent.getDisconnectedCV();
         mCm.unregisterNetworkCallback(networkCallback);
@@ -1820,6 +1975,52 @@
     }
 
     @SmallTest
+    public void testCaptivePortalApp() {
+        final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+        final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+        mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+        final TestNetworkCallback validatedCallback = new TestNetworkCallback();
+        final NetworkRequest validatedRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_VALIDATED).build();
+        mCm.registerNetworkCallback(validatedRequest, validatedCallback);
+
+        // Bring up wifi.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true);
+        validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+
+        // Check that calling startCaptivePortalApp does nothing.
+        final int fastTimeoutMs = 100;
+        mCm.startCaptivePortalApp(wifiNetwork);
+        mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
+
+        // Turn into a captive portal.
+        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
+        mCm.reportNetworkConnectivity(wifiNetwork, false);
+        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // Check that startCaptivePortalApp sends the expected intent.
+        mCm.startCaptivePortalApp(wifiNetwork);
+        Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
+        assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
+        assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK));
+
+        // Have the app report that the captive portal is dismissed, and check that we revalidate.
+        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
+        CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
+        c.reportCaptivePortalDismissed();
+        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        mCm.unregisterNetworkCallback(validatedCallback);
+        mCm.unregisterNetworkCallback(captivePortalCallback);
+    }
+
+    @SmallTest
     public void testAvoidOrIgnoreCaptivePortals() {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -1866,16 +2067,19 @@
 
     @SmallTest
     public void testNetworkSpecifier() {
-        NetworkRequest.Builder b = new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
         NetworkRequest rEmpty1 = newWifiRequestBuilder().build();
-        NetworkRequest rEmpty2 = newWifiRequestBuilder().setNetworkSpecifier(null).build();
+        NetworkRequest rEmpty2 = newWifiRequestBuilder().setNetworkSpecifier((String) null).build();
         NetworkRequest rEmpty3 = newWifiRequestBuilder().setNetworkSpecifier("").build();
+        NetworkRequest rEmpty4 = newWifiRequestBuilder().setNetworkSpecifier(
+            (NetworkSpecifier) null).build();
         NetworkRequest rFoo = newWifiRequestBuilder().setNetworkSpecifier("foo").build();
-        NetworkRequest rBar = newWifiRequestBuilder().setNetworkSpecifier("bar").build();
+        NetworkRequest rBar = newWifiRequestBuilder().setNetworkSpecifier(
+                new StringNetworkSpecifier("bar")).build();
 
         TestNetworkCallback cEmpty1 = new TestNetworkCallback();
         TestNetworkCallback cEmpty2 = new TestNetworkCallback();
         TestNetworkCallback cEmpty3 = new TestNetworkCallback();
+        TestNetworkCallback cEmpty4 = new TestNetworkCallback();
         TestNetworkCallback cFoo = new TestNetworkCallback();
         TestNetworkCallback cBar = new TestNetworkCallback();
         TestNetworkCallback[] emptyCallbacks = new TestNetworkCallback[] {
@@ -1884,6 +2088,7 @@
         mCm.registerNetworkCallback(rEmpty1, cEmpty1);
         mCm.registerNetworkCallback(rEmpty2, cEmpty2);
         mCm.registerNetworkCallback(rEmpty3, cEmpty3);
+        mCm.registerNetworkCallback(rEmpty4, cEmpty4);
         mCm.registerNetworkCallback(rFoo, cFoo);
         mCm.registerNetworkCallback(rBar, cBar);
 
@@ -1892,9 +2097,10 @@
         cEmpty1.expectAvailableCallbacks(mWiFiNetworkAgent);
         cEmpty2.expectAvailableCallbacks(mWiFiNetworkAgent);
         cEmpty3.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cEmpty4.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertNoCallbacks(cFoo, cBar);
 
-        mWiFiNetworkAgent.setNetworkSpecifier("foo");
+        mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("foo"));
         cFoo.expectAvailableCallbacks(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
             c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
@@ -1902,7 +2108,7 @@
         cFoo.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
         cFoo.assertNoCallback();
 
-        mWiFiNetworkAgent.setNetworkSpecifier("bar");
+        mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("bar"));
         cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         cBar.expectAvailableCallbacks(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
@@ -1922,32 +2128,97 @@
 
     @SmallTest
     public void testInvalidNetworkSpecifier() {
-        boolean execptionCalled = true;
-
         try {
             NetworkRequest.Builder builder = new NetworkRequest.Builder();
-            builder.setNetworkSpecifier(MATCH_ALL_REQUESTS_NETWORK_SPECIFIER);
-            execptionCalled = false;
-        } catch (IllegalArgumentException e) {
-            // do nothing - should get here
+            builder.setNetworkSpecifier(new MatchAllNetworkSpecifier());
+            fail("NetworkRequest builder with MatchAllNetworkSpecifier");
+        } catch (IllegalArgumentException expected) {
+            // expected
         }
 
-        assertTrue("NetworkRequest builder with MATCH_ALL_REQUESTS_NETWORK_SPECIFIER",
-                execptionCalled);
-
         try {
             NetworkCapabilities networkCapabilities = new NetworkCapabilities();
             networkCapabilities.addTransportType(TRANSPORT_WIFI)
-                    .setNetworkSpecifier(NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER);
+                    .setNetworkSpecifier(new MatchAllNetworkSpecifier());
             mService.requestNetwork(networkCapabilities, null, 0, null,
                     ConnectivityManager.TYPE_WIFI);
-            execptionCalled = false;
-        } catch (IllegalArgumentException e) {
-            // do nothing - should get here
+            fail("ConnectivityService requestNetwork with MatchAllNetworkSpecifier");
+        } catch (IllegalArgumentException expected) {
+            // expected
         }
 
-        assertTrue("ConnectivityService requestNetwork with MATCH_ALL_REQUESTS_NETWORK_SPECIFIER",
-                execptionCalled);
+        class NonParcelableSpecifier extends NetworkSpecifier {
+            public boolean satisfiedBy(NetworkSpecifier other) { return false; }
+        };
+        class ParcelableSpecifier extends NonParcelableSpecifier implements Parcelable {
+            @Override public int describeContents() { return 0; }
+            @Override public void writeToParcel(Parcel p, int flags) {}
+        }
+        NetworkRequest.Builder builder;
+
+        builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
+        try {
+            builder.setNetworkSpecifier(new NonParcelableSpecifier());
+            Parcel parcelW = Parcel.obtain();
+            builder.build().writeToParcel(parcelW, 0);
+            fail("Parceling a non-parcelable specifier did not throw an exception");
+        } catch (Exception e) {
+            // expected
+        }
+
+        builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
+        builder.setNetworkSpecifier(new ParcelableSpecifier());
+        NetworkRequest nr = builder.build();
+        assertNotNull(nr);
+
+        try {
+            Parcel parcelW = Parcel.obtain();
+            nr.writeToParcel(parcelW, 0);
+            byte[] bytes = parcelW.marshall();
+            parcelW.recycle();
+
+            Parcel parcelR = Parcel.obtain();
+            parcelR.unmarshall(bytes, 0, bytes.length);
+            parcelR.setDataPosition(0);
+            NetworkRequest rereadNr = NetworkRequest.CREATOR.createFromParcel(parcelR);
+            fail("Unparceling a non-framework NetworkSpecifier did not throw an exception");
+        } catch (Exception e) {
+            // expected
+        }
+    }
+
+    @SmallTest
+    public void testNetworkSpecifierUidSpoofSecurityException() {
+        class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+            @Override
+            public boolean satisfiedBy(NetworkSpecifier other) {
+                return true;
+            }
+
+            @Override
+            public void assertValidFromUid(int requestorUid) {
+                throw new SecurityException("failure");
+            }
+
+            @Override
+            public int describeContents() { return 0; }
+            @Override
+            public void writeToParcel(Parcel dest, int flags) {}
+        }
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+
+        UidAwareNetworkSpecifier networkSpecifier = new UidAwareNetworkSpecifier();
+        NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier(
+                networkSpecifier).build();
+        TestNetworkCallback networkCallback = new TestNetworkCallback();
+        try {
+            mCm.requestNetwork(networkRequest, networkCallback);
+            fail("Network request with spoofed UID did not throw a SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
     }
 
     @SmallTest
@@ -2051,7 +2322,7 @@
         ContentResolver cr = mServiceContext.getContentResolver();
         Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
         mService.updateMobileDataAlwaysOn();
-        mService.waitForIdle();
+        waitForIdle();
     }
 
     private boolean isForegroundNetwork(MockNetworkAgent network) {
@@ -2093,7 +2364,7 @@
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
         // When lingering is complete, cell is still there but is now in the background.
-        mService.waitForIdle();
+        waitForIdle();
         int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
         fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
         // Expect a network capabilities update sans FOREGROUND.
@@ -2149,6 +2420,11 @@
         //    and NUM_REQUESTS onAvailable callbacks to fire.
         // See how long it took.
         final int NUM_REQUESTS = 90;
+        final int REGISTER_TIME_LIMIT_MS = 180;
+        final int CONNECT_TIME_LIMIT_MS = 50;
+        final int SWITCH_TIME_LIMIT_MS = 50;
+        final int UNREGISTER_TIME_LIMIT_MS = 20;
+
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
         final NetworkCallback[] callbacks = new NetworkCallback[NUM_REQUESTS];
         final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS);
@@ -2161,14 +2437,12 @@
             };
         }
 
-        final int REGISTER_TIME_LIMIT_MS = 180;
         assertTimeLimit("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
             for (NetworkCallback cb : callbacks) {
                 mCm.registerNetworkCallback(request, cb);
             }
         });
 
-        final int CONNECT_TIME_LIMIT_MS = 40;
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         // Don't request that the network validate, because otherwise connect() will block until
         // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired,
@@ -2176,31 +2450,29 @@
         mCellNetworkAgent.connect(false);
 
         long onAvailableDispatchingDuration = durationOf(() -> {
-            if (!awaitLatch(availableLatch, CONNECT_TIME_LIMIT_MS)) {
-                fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
-                        NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
-                        CONNECT_TIME_LIMIT_MS));
-            }
+            awaitLatch(availableLatch, 10 * CONNECT_TIME_LIMIT_MS);
         });
-        Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS));
+        Log.d(TAG, String.format("Dispatched %d of %d onAvailable callbacks in %dms",
+                NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
+                onAvailableDispatchingDuration));
+        assertTrue(String.format("Dispatching %d onAvailable callbacks in %dms, expected %dms",
+                NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS),
+                onAvailableDispatchingDuration <= CONNECT_TIME_LIMIT_MS);
 
-        final int SWITCH_TIME_LIMIT_MS = 40;
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         // Give wifi a high enough score that we'll linger cell when wifi comes up.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.adjustScore(40);
         mWiFiNetworkAgent.connect(false);
 
         long onLostDispatchingDuration = durationOf(() -> {
-            if (!awaitLatch(losingLatch, SWITCH_TIME_LIMIT_MS)) {
-                fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
-                        NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
-            }
+            awaitLatch(losingLatch, 10 * SWITCH_TIME_LIMIT_MS);
         });
-        Log.d(TAG, String.format("Linger, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS));
+        Log.d(TAG, String.format("Dispatched %d of %d onLosing callbacks in %dms",
+                NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, onLostDispatchingDuration));
+        assertTrue(String.format("Dispatching %d onLosing callbacks in %dms, expected %dms",
+                NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS),
+                onLostDispatchingDuration <= SWITCH_TIME_LIMIT_MS);
 
-        final int UNREGISTER_TIME_LIMIT_MS = 10;
         assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
             for (NetworkCallback cb : callbacks) {
                 mCm.unregisterNetworkCallback(cb);
@@ -2223,9 +2495,7 @@
 
     private boolean awaitLatch(CountDownLatch l, long timeoutMs) {
         try {
-            if (l.await(timeoutMs, TimeUnit.MILLISECONDS)) {
-                return true;
-            }
+            return l.await(timeoutMs, TimeUnit.MILLISECONDS);
         } catch (InterruptedException e) {}
         return false;
     }
@@ -2277,7 +2547,7 @@
         assertFalse(testFactory.getMyStartRequested());  // Because the cell network outscores us.
 
         // Check that cell data stays up.
-        mService.waitForIdle();
+        waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertEquals(2, mCm.getAllNetworks().length);
 
@@ -2306,7 +2576,7 @@
         for (int i = 0; i < values.length; i++) {
             Settings.Global.putInt(cr, settingName, 1);
             tracker.reevaluate();
-            mService.waitForIdle();
+            waitForIdle();
             String msg = String.format("config=false, setting=%s", values[i]);
             assertTrue(mService.avoidBadWifi());
             assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
@@ -2316,19 +2586,19 @@
 
         Settings.Global.putInt(cr, settingName, 0);
         tracker.reevaluate();
-        mService.waitForIdle();
+        waitForIdle();
         assertFalse(mService.avoidBadWifi());
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putInt(cr, settingName, 1);
         tracker.reevaluate();
-        mService.waitForIdle();
+        waitForIdle();
         assertTrue(mService.avoidBadWifi());
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putString(cr, settingName, null);
         tracker.reevaluate();
-        mService.waitForIdle();
+        waitForIdle();
         assertFalse(mService.avoidBadWifi());
         assertTrue(tracker.shouldNotifyWifiUnvalidated());
     }
@@ -2471,7 +2741,7 @@
                 tracker.configMeteredMultipathPreference = config;
                 Settings.Global.putString(cr, settingName, setting);
                 tracker.reevaluate();
-                mService.waitForIdle();
+                waitForIdle();
 
                 final int expected = (setting != null) ? Integer.parseInt(setting) : config;
                 String msg = String.format("config=%d, setting=%s", config, setting);
@@ -2659,7 +2929,7 @@
         waitFor(cv);
         verifyActiveNetwork(TRANSPORT_WIFI);
         mWiFiNetworkAgent.sendLinkProperties(lp);
-        mService.waitForIdle();
+        waitForIdle();
         return mWiFiNetworkAgent.getNetwork();
     }
 
@@ -2738,7 +3008,7 @@
         callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
 
         // ... and that stopping it after that has no adverse effects.
-        mService.waitForIdle();
+        waitForIdle();
         final Network myNetAlias = myNet;
         assertNull(mCm.getNetworkCapabilities(myNetAlias));
         ka.stop();
@@ -2753,7 +3023,7 @@
         ka.stop();
         mWiFiNetworkAgent.disconnect();
         waitFor(mWiFiNetworkAgent.getDisconnectedCV());
-        mService.waitForIdle();
+        waitForIdle();
         callback.expectStopped();
 
         // Reconnect.
@@ -2905,7 +3175,7 @@
                 networkCallbacks.add(networkCallback);
             }
             fail("Registering " + MAX_REQUESTS + " NetworkRequests did not throw exception");
-        } catch (IllegalArgumentException expected) {}
+        } catch (TooManyRequestsException expected) {}
         for (NetworkCallback networkCallback : networkCallbacks) {
             mCm.unregisterNetworkCallback(networkCallback);
         }
@@ -2918,7 +3188,7 @@
                 networkCallbacks.add(networkCallback);
             }
             fail("Registering " + MAX_REQUESTS + " NetworkCallbacks did not throw exception");
-        } catch (IllegalArgumentException expected) {}
+        } catch (TooManyRequestsException expected) {}
         for (NetworkCallback networkCallback : networkCallbacks) {
             mCm.unregisterNetworkCallback(networkCallback);
         }
@@ -2934,7 +3204,7 @@
             }
             fail("Registering " + MAX_REQUESTS +
                     " PendingIntent NetworkRequests did not throw exception");
-        } catch (IllegalArgumentException expected) {}
+        } catch (TooManyRequestsException expected) {}
         for (PendingIntent pendingIntent : pendingIntents) {
             mCm.unregisterNetworkCallback(pendingIntent);
         }
@@ -2949,12 +3219,12 @@
             }
             fail("Registering " + MAX_REQUESTS +
                     " PendingIntent NetworkCallbacks did not throw exception");
-        } catch (IllegalArgumentException expected) {}
+        } catch (TooManyRequestsException expected) {}
         for (PendingIntent pendingIntent : pendingIntents) {
             mCm.unregisterNetworkCallback(pendingIntent);
         }
         pendingIntents.clear();
-        mService.waitForIdle(5000);
+        waitForIdle(5000);
 
         // Test that the limit is not hit when MAX_REQUESTS requests are added and removed.
         for (int i = 0; i < MAX_REQUESTS; i++) {
@@ -2962,20 +3232,20 @@
             mCm.requestNetwork(networkRequest, networkCallback);
             mCm.unregisterNetworkCallback(networkCallback);
         }
-        mService.waitForIdle();
+        waitForIdle();
         for (int i = 0; i < MAX_REQUESTS; i++) {
             NetworkCallback networkCallback = new NetworkCallback();
             mCm.registerNetworkCallback(networkRequest, networkCallback);
             mCm.unregisterNetworkCallback(networkCallback);
         }
-        mService.waitForIdle();
+        waitForIdle();
         for (int i = 0; i < MAX_REQUESTS; i++) {
             PendingIntent pendingIntent =
                     PendingIntent.getBroadcast(mContext, 0, new Intent("b" + i), 0);
             mCm.requestNetwork(networkRequest, pendingIntent);
             mCm.unregisterNetworkCallback(pendingIntent);
         }
-        mService.waitForIdle();
+        waitForIdle();
         for (int i = 0; i < MAX_REQUESTS; i++) {
             PendingIntent pendingIntent =
                     PendingIntent.getBroadcast(mContext, 0, new Intent("c" + i), 0);
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/connectivity/NetworkNotificationManagerTest.java
rename to tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 21c2de7..f201bc7 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -91,7 +91,7 @@
         final int NETWORK_ID_BASE = 100;
         List<NotificationType> types = Arrays.asList(NotificationType.values());
         List<Integer> ids = new ArrayList<>(types.size());
-        for (int i = 0; i < ids.size(); i++) {
+        for (int i = 0; i < types.size(); i++) {
             ids.add(NETWORK_ID_BASE + i);
         }
         Collections.shuffle(ids);
@@ -101,9 +101,10 @@
             mManager.showNotification(ids.get(i), types.get(i), mWifiNai, mCellNai, null, false);
         }
 
-        Collections.shuffle(ids);
+        List<Integer> idsToClear = new ArrayList<>(ids);
+        Collections.shuffle(idsToClear);
         for (int i = 0; i < ids.size(); i++) {
-            mManager.clearNotification(ids.get(i));
+            mManager.clearNotification(idsToClear.get(i));
         }
 
         for (int i = 0; i < ids.size(); i++) {
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index a9f68c8..bc89c0f 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -16,63 +16,142 @@
 
 package com.android.server.connectivity;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Resources;
+import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.PersistableBundle;
+import android.os.RemoteException;
 import android.os.test.TestLooper;
+import android.os.UserHandle;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.telephony.CarrierConfigManager;
 
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.server.connectivity.tethering.OffloadHardwareInterface;
+import com.android.server.connectivity.tethering.TetheringDependencies;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.Vector;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class TetheringTest {
     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
 
     @Mock private Context mContext;
+    @Mock private ConnectivityManager mConnectivityManager;
     @Mock private INetworkManagementService mNMService;
     @Mock private INetworkStatsService mStatsService;
     @Mock private INetworkPolicyManager mPolicyManager;
     @Mock private MockableSystemProperties mSystemProperties;
+    @Mock private OffloadHardwareInterface mOffloadHardwareInterface;
     @Mock private Resources mResources;
+    @Mock private TetheringDependencies mTetheringDependencies;
+    @Mock private UsbManager mUsbManager;
+    @Mock private WifiManager mWifiManager;
     @Mock private CarrierConfigManager mCarrierConfigManager;
 
     // Like so many Android system APIs, these cannot be mocked because it is marked final.
     // We have to use the real versions.
     private final PersistableBundle mCarrierConfig = new PersistableBundle();
     private final TestLooper mLooper = new TestLooper();
+    private final String mTestIfname = "test_wlan0";
 
+    private Vector<Intent> mIntents;
+    private BroadcastInterceptingContext mServiceContext;
+    private BroadcastReceiver mBroadcastReceiver;
     private Tethering mTethering;
 
-    @Before public void setUp() throws Exception {
+    private class MockContext extends BroadcastInterceptingContext {
+        MockContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Resources getResources() { return mResources; }
+
+        @Override
+        public Object getSystemService(String name) {
+            if (Context.CONNECTIVITY_SERVICE.equals(name)) return mConnectivityManager;
+            if (Context.WIFI_SERVICE.equals(name)) return mWifiManager;
+            return super.getSystemService(name);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
                 .thenReturn(new String[0]);
         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[0]);
+                .thenReturn(new String[]{ "test_wlan\\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))
                 .thenReturn(new int[0]);
-        mTethering = new Tethering(mContext, mNMService, mStatsService, mPolicyManager,
-                                   mLooper.getLooper(), mSystemProperties);
+        when(mNMService.listInterfaces())
+                .thenReturn(new String[]{ "test_rmnet_data0", mTestIfname });
+        when(mNMService.getInterfaceConfig(anyString()))
+                .thenReturn(new InterfaceConfiguration());
+
+        mServiceContext = new MockContext(mContext);
+        mIntents = new Vector<>();
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mIntents.addElement(intent);
+            }
+        };
+        mServiceContext.registerReceiver(mBroadcastReceiver,
+                new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+        when(mTetheringDependencies.getOffloadHardwareInterface())
+                .thenReturn(mOffloadHardwareInterface);
+        mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
+                                   mLooper.getLooper(), mSystemProperties,
+                                   mTetheringDependencies);
+    }
+
+    @After
+    public void tearDown() {
+        mServiceContext.unregisterReceiver(mBroadcastReceiver);
     }
 
     private void setupForRequiredProvisioning() {
@@ -126,4 +205,212 @@
                 .thenReturn(new String[] {"malformedApp"});
         assertTrue(!mTethering.isTetherProvisioningRequired());
     }
+
+    private void sendWifiApStateChanged(int state) {
+        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, state);
+        mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private void verifyInterfaceServingModeStarted() throws Exception {
+        verify(mNMService, times(1)).listInterfaces();
+        verify(mNMService, times(1)).getInterfaceConfig(mTestIfname);
+        verify(mNMService, times(1))
+                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
+        verify(mNMService, times(1)).tetherInterface(mTestIfname);
+    }
+
+    private void verifyTetheringBroadcast(String ifname, String whichExtra) {
+        // Verify that ifname is in the whichExtra array of the tether state changed broadcast.
+        final Intent bcast = mIntents.get(0);
+        assertEquals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED, bcast.getAction());
+        final ArrayList<String> ifnames = bcast.getStringArrayListExtra(whichExtra);
+        assertTrue(ifnames.contains(ifname));
+        mIntents.remove(bcast);
+    }
+
+    @Test
+    public void workingLocalOnlyHotspot() throws Exception {
+        when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
+
+        // Emulate externally-visible WifiManager effects, causing the
+        // per-interface state machine to start up, and telling us that
+        // hotspot mode is to be started.
+        mTethering.interfaceStatusChanged(mTestIfname, true);
+        sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED);
+        mLooper.dispatchAll();
+
+        verifyInterfaceServingModeStarted();
+        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+        verify(mNMService, times(1)).setIpForwardingEnabled(true);
+        verify(mNMService, times(1)).startTethering(any(String[].class));
+        verifyNoMoreInteractions(mNMService);
+        verify(mWifiManager).updateInterfaceIpState(
+                mTestIfname, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+        verifyNoMoreInteractions(mWifiManager);
+        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY);
+        // UpstreamNetworkMonitor will be started, and will register two callbacks:
+        // a "listen all" and a "track default".
+        verify(mConnectivityManager, times(1)).registerNetworkCallback(
+                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
+        verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback(
+                any(NetworkCallback.class), any(Handler.class));
+        // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
+        verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
+        verifyNoMoreInteractions(mConnectivityManager);
+
+        // Emulate externally-visible WifiManager effects, when hotspot mode
+        // is being torn down.
+        sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+        mTethering.interfaceRemoved(mTestIfname);
+        mLooper.dispatchAll();
+
+        verify(mNMService, times(1)).untetherInterface(mTestIfname);
+        // TODO: Why is {g,s}etInterfaceConfig() called more than once?
+        verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname);
+        verify(mNMService, atLeastOnce())
+                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
+        verify(mNMService, times(1)).stopTethering();
+        verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mWifiManager);
+        // Asking for the last error after the per-interface state machine
+        // has been reaped yields an unknown interface error.
+        assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
+                mTethering.getLastTetherError(mTestIfname));
+    }
+
+    @Test
+    public void workingWifiTethering() throws Exception {
+        when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
+        when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+
+        // Emulate pressing the WiFi tethering button.
+        mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
+        mLooper.dispatchAll();
+        verify(mWifiManager, times(1)).startSoftAp(null);
+        verifyNoMoreInteractions(mWifiManager);
+        verifyNoMoreInteractions(mConnectivityManager);
+        verifyNoMoreInteractions(mNMService);
+
+        // Emulate externally-visible WifiManager effects, causing the
+        // per-interface state machine to start up, and telling us that
+        // tethering mode is to be started.
+        mTethering.interfaceStatusChanged(mTestIfname, true);
+        sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED);
+        mLooper.dispatchAll();
+
+        verifyInterfaceServingModeStarted();
+        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+        verify(mNMService, times(1)).setIpForwardingEnabled(true);
+        verify(mNMService, times(1)).startTethering(any(String[].class));
+        verifyNoMoreInteractions(mNMService);
+        verify(mWifiManager).updateInterfaceIpState(
+                mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED);
+        verifyNoMoreInteractions(mWifiManager);
+        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_TETHER);
+        // UpstreamNetworkMonitor will be started, and will register two callbacks:
+        // a "listen all" and a "track default".
+        verify(mConnectivityManager, times(1)).registerNetworkCallback(
+                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
+        verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback(
+                any(NetworkCallback.class), any(Handler.class));
+        // In tethering mode, in the default configuration, an explicit request
+        // for a mobile network is also made.
+        verify(mConnectivityManager, atLeastOnce()).getNetworkInfo(anyInt());
+        verify(mConnectivityManager, times(1)).requestNetwork(
+                any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(),
+                any(Handler.class));
+        // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
+        verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
+        verifyNoMoreInteractions(mConnectivityManager);
+
+        /////
+        // We do not currently emulate any upstream being found.
+        //
+        // This is why there are no calls to verify mNMService.enableNat() or
+        // mNMService.startInterfaceForwarding().
+        /////
+
+        // Emulate pressing the WiFi tethering button.
+        mTethering.stopTethering(ConnectivityManager.TETHERING_WIFI);
+        mLooper.dispatchAll();
+        verify(mWifiManager, times(1)).stopSoftAp();
+        verifyNoMoreInteractions(mWifiManager);
+        verifyNoMoreInteractions(mConnectivityManager);
+        verifyNoMoreInteractions(mNMService);
+
+        // Emulate externally-visible WifiManager effects, when tethering mode
+        // is being torn down.
+        sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+        mTethering.interfaceRemoved(mTestIfname);
+        mLooper.dispatchAll();
+
+        verify(mNMService, times(1)).untetherInterface(mTestIfname);
+        // TODO: Why is {g,s}etInterfaceConfig() called more than once?
+        verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname);
+        verify(mNMService, atLeastOnce())
+                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
+        verify(mNMService, times(1)).stopTethering();
+        verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mWifiManager);
+        // Asking for the last error after the per-interface state machine
+        // has been reaped yields an unknown interface error.
+        assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
+                mTethering.getLastTetherError(mTestIfname));
+    }
+
+    @Test
+    public void failureEnablingIpForwarding() throws Exception {
+        when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
+        when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+        doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true);
+
+        // Emulate pressing the WiFi tethering button.
+        mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
+        mLooper.dispatchAll();
+        verify(mWifiManager, times(1)).startSoftAp(null);
+        verifyNoMoreInteractions(mWifiManager);
+        verifyNoMoreInteractions(mConnectivityManager);
+        verifyNoMoreInteractions(mNMService);
+
+        // Emulate externally-visible WifiManager effects, causing the
+        // per-interface state machine to start up, and telling us that
+        // tethering mode is to be started.
+        mTethering.interfaceStatusChanged(mTestIfname, true);
+        sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED);
+        mLooper.dispatchAll();
+
+        // Activity caused by test_wlan0 becoming available.
+        verify(mNMService, times(1)).listInterfaces();
+        // We verify get/set called twice here: once for setup and once during
+        // teardown because all events happen over the course of the single
+        // dispatchAll() above.
+        verify(mNMService, times(2)).getInterfaceConfig(mTestIfname);
+        verify(mNMService, times(2))
+                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
+        verify(mNMService, times(1)).tetherInterface(mTestIfname);
+        verify(mWifiManager).updateInterfaceIpState(
+                mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED);
+        verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
+        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+        // This is called, but will throw.
+        verify(mNMService, times(1)).setIpForwardingEnabled(true);
+        // This never gets called because of the exception thrown above.
+        verify(mNMService, times(0)).startTethering(any(String[].class));
+        // When the master state machine transitions to an error state it tells
+        // downstream interfaces, which causes us to tell Wi-Fi about the error
+        // so it can take down AP mode.
+        verify(mNMService, times(1)).untetherInterface(mTestIfname);
+        verify(mWifiManager).updateInterfaceIpState(
+                mTestIfname, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
+
+        verifyNoMoreInteractions(mWifiManager);
+        verifyNoMoreInteractions(mConnectivityManager);
+        verifyNoMoreInteractions(mNMService);
+    }
+
+    // TODO: Test that a request for hotspot mode doesn't interfere with an
+    // already operating tethering mode interface.
 }
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index efe6fec..506d9e5 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -27,6 +27,7 @@
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -42,6 +43,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.internal.net.VpnConfig;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Map;
@@ -101,8 +104,10 @@
     @Override
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         setMockedPackages(mPackages);
+
         when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
         when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
@@ -258,6 +263,58 @@
     }
 
     @SmallTest
+    public void testLockdownRuleRepeatability() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+
+        // Given legacy lockdown is already enabled,
+        vpn.setLockdown(true);
+        verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+                eq(true), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+        // Enabling legacy lockdown twice should do nothing.
+        vpn.setLockdown(true);
+        verify(mNetService, times(1)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+
+        // And disabling should remove the rules exactly once.
+        vpn.setLockdown(false);
+        verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+                eq(false), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+        // Removing the lockdown again should have no effect.
+        vpn.setLockdown(false);
+        verify(mNetService, times(2)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+    }
+
+    @SmallTest
+    public void testLockdownRuleReversibility() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+
+        final UidRange[] entireUser = {
+            UidRange.createForUser(primaryUser.id)
+        };
+        final UidRange[] exceptPkg0 = {
+            new UidRange(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
+            new UidRange(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
+        };
+
+        final InOrder order = inOrder(mNetService);
+
+        // Given lockdown is enabled with no package (legacy VPN),
+        vpn.setLockdown(true);
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+
+        // When a new VPN package is set the rules should change to cover that package.
+        vpn.prepare(null, PKGS[0]);
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser));
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0));
+
+        // When that VPN package is unset, everything should be undone again in reverse.
+        vpn.prepare(null, VpnConfig.LEGACY_VPN);
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0));
+        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+    }
+
+    @SmallTest
     public void testNotificationShownForAlwaysOnApp() {
         final UserHandle userHandle = UserHandle.of(primaryUser.id);
         final Vpn vpn = createVpn(primaryUser.id);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
new file mode 100644
index 0000000..b5d333b
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static com.android.internal.telephony.IccCardConstants.INTENT_VALUE_ICC_ABSENT;
+import static com.android.internal.telephony.IccCardConstants.INTENT_VALUE_ICC_LOADED;
+import static com.android.internal.telephony.IccCardConstants.INTENT_KEY_ICC_STATE;
+import static com.android.internal.telephony.TelephonyIntents.ACTION_SIM_STATE_CHANGED;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.reset;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SimChangeListenerTest {
+    private static final int EVENT_UNM_UPDATE = 1;
+
+    @Mock private Context mContext;
+    private BroadcastInterceptingContext mServiceContext;
+    private Handler mHandler;
+    private SimChangeListener mSCL;
+    private int mCallbackCount;
+
+    private void doCallback() { mCallbackCount++; }
+
+    private class MockContext extends BroadcastInterceptingContext {
+        MockContext(Context base) {
+            super(base);
+        }
+    }
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+    }
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        reset(mContext);
+        mServiceContext = new MockContext(mContext);
+        mHandler = new Handler(Looper.myLooper());
+        mCallbackCount = 0;
+        mSCL = new SimChangeListener(mServiceContext, mHandler, () -> doCallback());
+    }
+
+    @After public void tearDown() throws Exception {
+        if (mSCL != null) {
+            mSCL.stopListening();
+            mSCL = null;
+        }
+    }
+
+    private void sendSimStateChangeIntent(String state) {
+        final Intent intent = new Intent(ACTION_SIM_STATE_CHANGED);
+        intent.putExtra(INTENT_KEY_ICC_STATE, state);
+        mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    @Test
+    public void testNotSeenFollowedBySeenCallsCallback() {
+        mSCL.startListening();
+
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+        assertEquals(1, mCallbackCount);
+
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+        assertEquals(2, mCallbackCount);
+
+        mSCL.stopListening();
+    }
+
+    @Test
+    public void testNotListeningDoesNotCallback() {
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+        assertEquals(0, mCallbackCount);
+
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+        assertEquals(0, mCallbackCount);
+    }
+
+    @Test
+    public void testSeenOnlyDoesNotCallback() {
+        mSCL.startListening();
+
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+        assertEquals(0, mCallbackCount);
+
+        sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+        assertEquals(0, mCallbackCount);
+
+        mSCL.stopListening();
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
index 32e1b96..27e683c 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -38,6 +38,7 @@
 import android.net.ConnectivityManager;
 import android.net.INetworkStatsService;
 import android.net.InterfaceConfiguration;
+import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
@@ -63,12 +64,14 @@
     @Mock private IControlsTethering mTetherHelper;
     @Mock private InterfaceConfiguration mInterfaceConfiguration;
     @Mock private IPv6TetheringInterfaceServices mIPv6TetheringInterfaceServices;
+    @Mock private SharedLog mSharedLog;
 
     private final TestLooper mLooper = new TestLooper();
     private TetherInterfaceStateMachine mTestedSm;
 
     private void initStateMachine(int interfaceType) throws Exception {
-        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType,
+        mTestedSm = new TetherInterfaceStateMachine(
+                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
                 mNMService, mStatsService, mTetherHelper, mIPv6TetheringInterfaceServices);
         mTestedSm.start();
         // Starting the state machine always puts us in a consistent state and notifies
@@ -80,7 +83,7 @@
 
     private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
         initStateMachine(interfaceType);
-        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
         if (upstreamIface != null) {
             dispatchTetherConnectionChanged(upstreamIface);
         }
@@ -90,12 +93,13 @@
 
     @Before public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
     }
 
     @Test
     public void startsOutAvailable() {
         mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
-                TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper,
+                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper,
                 mIPv6TetheringInterfaceServices);
         mTestedSm.start();
         mLooper.dispatchAll();
@@ -138,7 +142,7 @@
     public void canBeTethered() throws Exception {
         initStateMachine(TETHERING_BLUETOOTH);
 
-        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
         InOrder inOrder = inOrder(mTetherHelper, mNMService);
         inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
         inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
@@ -162,7 +166,7 @@
     public void canBeTetheredAsUsb() throws Exception {
         initStateMachine(TETHERING_USB);
 
-        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
         InOrder inOrder = inOrder(mTetherHelper, mNMService);
         inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
         inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
@@ -272,7 +276,7 @@
         initStateMachine(TETHERING_USB);
 
         doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
-        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
         InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
         usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
         usbTeardownOrder.verify(mNMService).setInterfaceConfig(
@@ -310,6 +314,17 @@
      * Send a command to the state machine under test, and run the event loop to idle.
      *
      * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
+     * @param obj An additional argument to pass.
+     */
+    private void dispatchCommand(int command, int arg1) {
+        mTestedSm.sendMessage(command, arg1);
+        mLooper.dispatchAll();
+    }
+
+    /**
+     * Send a command to the state machine under test, and run the event loop to idle.
+     *
+     * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
      */
     private void dispatchCommand(int command) {
         mTestedSm.sendMessage(command);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
new file mode 100644
index 0000000..9fcd1b5
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static com.android.server.connectivity.tethering.TetheringConfiguration.DUN_NOT_REQUIRED;
+import static com.android.server.connectivity.tethering.TetheringConfiguration.DUN_REQUIRED;
+import static com.android.server.connectivity.tethering.TetheringConfiguration.DUN_UNSPECIFIED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TetheringConfigurationTest {
+    @Mock private Context mContext;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private Resources mResources;
+    private Context mMockContext;
+    private boolean mHasTelephonyManager;
+
+    private class MockContext extends BroadcastInterceptingContext {
+        MockContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Resources getResources() { return mResources; }
+
+        @Override
+        public Object getSystemService(String name) {
+            if (Context.TELEPHONY_SERVICE.equals(name)) {
+                return mHasTelephonyManager ? mTelephonyManager : null;
+            }
+            return super.getSystemService(name);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
+                .thenReturn(new String[0]);
+        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" });
+        when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
+                .thenReturn(new String[0]);
+        mMockContext = new MockContext(mContext);
+    }
+
+    @Test
+    public void testDunFromTelephonyManagerMeansDun() {
+        when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
+                .thenReturn(new int[]{TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI});
+        mHasTelephonyManager = true;
+        when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_REQUIRED);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext);
+        assertTrue(cfg.isDunRequired);
+        assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
+        assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
+        assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
+        // Just to prove we haven't clobbered Wi-Fi:
+        assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI));
+    }
+
+    @Test
+    public void testDunNotRequiredFromTelephonyManagerMeansNoDun() {
+        when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
+                .thenReturn(new int[]{TYPE_MOBILE_DUN, TYPE_WIFI});
+        mHasTelephonyManager = true;
+        when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_NOT_REQUIRED);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext);
+        assertFalse(cfg.isDunRequired);
+        assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
+        assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
+        assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
+        // Just to prove we haven't clobbered Wi-Fi:
+        assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI));
+    }
+
+    @Test
+    public void testDunFromUpstreamConfigMeansDun() {
+        when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
+                .thenReturn(new int[]{TYPE_MOBILE_DUN, TYPE_WIFI});
+        mHasTelephonyManager = false;
+        when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_UNSPECIFIED);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext);
+        assertTrue(cfg.isDunRequired);
+        assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
+        // Just to prove we haven't clobbered Wi-Fi:
+        assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI));
+    }
+}
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 c72efb0..9bb392a 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -25,11 +25,13 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.os.Handler;
@@ -40,6 +42,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.util.SharedLog;
 
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -69,6 +72,7 @@
 
     @Mock private Context mContext;
     @Mock private IConnectivityManager mCS;
+    @Mock private SharedLog mLog;
 
     private TestStateMachine mSM;
     private TestConnectivityManager mCM;
@@ -78,10 +82,12 @@
         MockitoAnnotations.initMocks(this);
         reset(mContext);
         reset(mCS);
+        reset(mLog);
+        when(mLog.forSubComponent(anyString())).thenReturn(mLog);
 
         mCM = spy(new TestConnectivityManager(mContext, mCS));
         mSM = new TestStateMachine();
-        mUNM = new UpstreamNetworkMonitor(mSM, EVENT_UNM_UPDATE, (ConnectivityManager) mCM);
+        mUNM = new UpstreamNetworkMonitor(mSM, EVENT_UNM_UPDATE, (ConnectivityManager) mCM, mLog);
     }
 
     @After public void tearDown() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
similarity index 91%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
index bb8f9d1..23318c2 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
@@ -25,16 +26,22 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 
 import com.android.server.LocalServices;
 
-import junit.framework.TestCase;
-
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-public class NetworkStatsAccessTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsAccessTest {
     private static final String TEST_PKG = "com.example.test";
     private static final int TEST_UID = 12345;
 
@@ -46,9 +53,8 @@
     // Hold the real service so we can restore it when tearing down the test.
     private DevicePolicyManagerInternal mSystemDpmi;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         MockitoAnnotations.initMocks(this);
 
         mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
@@ -59,13 +65,13 @@
         when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
     }
 
-    @Override
+    @After
     public void tearDown() throws Exception {
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
         LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
-        super.tearDown();
     }
 
+    @Test
     public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
         setHasCarrierPrivileges(true);
         setIsDeviceOwner(false);
@@ -76,6 +82,7 @@
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_isDeviceOwner() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(true);
@@ -86,6 +93,7 @@
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_isProfileOwner() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
@@ -96,36 +104,40 @@
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
         setIsProfileOwner(true);
         setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
         setHasReadHistoryPermission(false);
-        assertEquals(NetworkStatsAccess.Level.USER,
+        assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
         setIsProfileOwner(true);
         setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
         setHasReadHistoryPermission(false);
-        assertEquals(NetworkStatsAccess.Level.USER,
+        assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
         setIsProfileOwner(true);
         setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
         setHasReadHistoryPermission(true);
-        assertEquals(NetworkStatsAccess.Level.USER,
+        assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
@@ -136,6 +148,7 @@
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
     }
 
+    @Test
     public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
         setHasCarrierPrivileges(false);
         setIsDeviceOwner(false);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 9f53c87..2a32b73 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -31,11 +31,11 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
+import android.support.test.filters.SmallTest;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.MediumTest;
 
-import com.android.frameworks.servicestests.R;
+import com.android.frameworks.tests.net.R;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -51,7 +51,7 @@
 /**
  * Tests for {@link NetworkStatsCollection}.
  */
-@MediumTest
+@SmallTest
 public class NetworkStatsCollectionTest extends AndroidTestCase {
 
     private static final String TEST_FILE = "test.bin";
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index 5eee7b9..92dcdac 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -18,36 +18,39 @@
 
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.when;
-
-import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+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.anyInt;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.when;
+
 import android.app.usage.NetworkStatsManager;
 import android.net.DataUsageRequest;
 import android.net.NetworkIdentity;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Process;
-
-import android.os.ConditionVariable;
 import android.os.Looper;
-import android.os.Messenger;
 import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
 import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 
@@ -60,8 +63,9 @@
 import java.util.Objects;
 import java.util.List;
 
-import junit.framework.TestCase;
-
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -69,7 +73,9 @@
 /**
  * Tests for {@link NetworkStatsObservers}.
  */
-public class NetworkStatsObserversTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsObserversTest {
     private static final String TEST_IFACE = "test0";
     private static final String TEST_IFACE2 = "test1";
     private static final long TEST_START = 1194220800000L;
@@ -87,7 +93,7 @@
     private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
     private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
 
-    private static final long WAIT_TIMEOUT = 500;  // 1/2 sec
+    private static final long WAIT_TIMEOUT_MS = 500;
     private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
     private static final long BASE_BYTES = 7 * MB_IN_BYTES;
     private static final int INVALID_TYPE = -1;
@@ -100,7 +106,6 @@
     private Handler mObserverNoopHandler;
 
     private LatchedHandler mHandler;
-    private ConditionVariable mCv;
 
     private NetworkStatsObservers mStatsObservers;
     private Messenger mMessenger;
@@ -109,9 +114,8 @@
 
     @Mock private IBinder mockBinder;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         MockitoAnnotations.initMocks(this);
 
         mObserverHandlerThread = new IdleableHandlerThread("HandlerThread");
@@ -124,14 +128,14 @@
             }
         };
 
-        mCv = new ConditionVariable();
-        mHandler = new LatchedHandler(Looper.getMainLooper(), mCv);
+        mHandler = new LatchedHandler(Looper.getMainLooper(), new ConditionVariable());
         mMessenger = new Messenger(mHandler);
 
         mActiveIfaces = new ArrayMap<>();
         mActiveUidIfaces = new ArrayMap<>();
     }
 
+    @Test
     public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
         long thresholdTooLowBytes = 1L;
         DataUsageRequest inputRequest = new DataUsageRequest(
@@ -144,6 +148,7 @@
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
     }
 
+    @Test
     public void testRegister_highThreshold_accepted() throws Exception {
         long highThresholdBytes = 2 * THRESHOLD_BYTES;
         DataUsageRequest inputRequest = new DataUsageRequest(
@@ -156,6 +161,7 @@
         assertEquals(highThresholdBytes, request.thresholdInBytes);
     }
 
+    @Test
     public void testRegister_twoRequests_twoIds() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
@@ -173,6 +179,7 @@
         assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
     }
 
+    @Test
     public void testUnregister_unknownRequest_noop() throws Exception {
         DataUsageRequest unknownRequest = new DataUsageRequest(
                 123456 /* id */, sTemplateWifi, THRESHOLD_BYTES);
@@ -180,6 +187,7 @@
         mStatsObservers.unregister(unknownRequest, UID_RED);
     }
 
+    @Test
     public void testUnregister_knownRequest_releasesCaller() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -197,6 +205,7 @@
         Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
     }
 
+    @Test
     public void testUnregister_knownRequest_invalidUid_doesNotUnregister() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -214,6 +223,7 @@
         Mockito.verifyZeroInteractions(mockBinder);
     }
 
+    @Test
     public void testUpdateStats_initialSample_doesNotNotify() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -239,11 +249,9 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
-        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_belowThreshold_doesNotNotify() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -275,12 +283,10 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
-        mCv.block(WAIT_TIMEOUT);
-        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
     }
 
+
+    @Test
     public void testUpdateStats_deviceAccess_notifies() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -313,11 +319,10 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
         assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_defaultAccess_notifiesSameUid() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -351,11 +356,10 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
         assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_defaultAccess_usageOtherUid_doesNotNotify() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -389,11 +393,9 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
-        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_userAccess_usageSameUser_notifies() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -427,11 +429,10 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
         assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
     }
 
+    @Test
     public void testUpdateStats_userAccess_usageAnotherUser_doesNotNotify() throws Exception {
         DataUsageRequest inputRequest = new DataUsageRequest(
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
@@ -466,14 +467,22 @@
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
         waitForObserverToIdle();
-
-        assertTrue(mCv.block(WAIT_TIMEOUT));
-        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
     }
 
     private void waitForObserverToIdle() {
-        // Send dummy message to make sure that any previous message has been handled
-        mHandler.sendMessage(mHandler.obtainMessage(-1));
-        mObserverHandlerThread.waitForIdle(WAIT_TIMEOUT);
+        waitForIdleLooper(mObserverHandlerThread.getLooper(), WAIT_TIMEOUT_MS);
+        waitForIdleLooper(mHandler.getLooper(), 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/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 728eb73..029693f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -85,6 +85,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
@@ -119,6 +120,7 @@
  * still uses the Easymock structure, which could be simplified.
  */
 @RunWith(AndroidJUnit4.class)
+@SmallTest
 public class NetworkStatsServiceTest {
     private static final String TAG = "NetworkStatsServiceTest";
 
diff --git a/core/tests/coretests/res/raw/history_v1 b/tests/net/res/raw/history_v1
similarity index 100%
rename from core/tests/coretests/res/raw/history_v1
rename to tests/net/res/raw/history_v1
Binary files differ
diff --git a/services/tests/servicestests/res/raw/netstats_uid_v4 b/tests/net/res/raw/netstats_uid_v4
similarity index 100%
rename from services/tests/servicestests/res/raw/netstats_uid_v4
rename to tests/net/res/raw/netstats_uid_v4
Binary files differ
diff --git a/services/tests/servicestests/res/raw/netstats_v1 b/tests/net/res/raw/netstats_v1
similarity index 100%
rename from services/tests/servicestests/res/raw/netstats_v1
rename to tests/net/res/raw/netstats_v1
Binary files differ
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_iface_fmt_typical b/tests/net/res/raw/xt_qtaguid_iface_fmt_typical
similarity index 100%
rename from core/tests/coretests/res/raw/xt_qtaguid_iface_fmt_typical
rename to tests/net/res/raw/xt_qtaguid_iface_fmt_typical
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_iface_typical b/tests/net/res/raw/xt_qtaguid_iface_typical
similarity index 100%
rename from core/tests/coretests/res/raw/xt_qtaguid_iface_typical
rename to tests/net/res/raw/xt_qtaguid_iface_typical
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_typical b/tests/net/res/raw/xt_qtaguid_typical
similarity index 100%
rename from core/tests/coretests/res/raw/xt_qtaguid_typical
rename to tests/net/res/raw/xt_qtaguid_typical
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat b/tests/net/res/raw/xt_qtaguid_with_clat
new file mode 100644
index 0000000..77e5c7b
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_with_clat
@@ -0,0 +1,43 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 v4-wlan0 0x0 0 0 256 5 196 4 256 5 0 0 0 0 196 4 0 0 0 0
+3 v4-wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 v4-wlan0 0x0 1000 0 30312 25 1770 27 30236 24 76 1 0 0 1694 26 76 1 0 0
+5 v4-wlan0 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+6 v4-wlan0 0x0 10060 0 9398432 6717 169412 4235 9398432 6717 0 0 0 0 169412 4235 0 0 0 0
+7 v4-wlan0 0x0 10060 1 1448660 1041 31192 753 1448660 1041 0 0 0 0 31192 753 0 0 0 0
+8 v4-wlan0 0x0 10102 0 9702 16 2870 23 9702 16 0 0 0 0 2870 23 0 0 0 0
+9 v4-wlan0 0x0 10102 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+10 wlan0 0x0 0 0 11058671 7892 312046 5113 11043898 7811 13117 61 1656 20 306544 5046 3230 38 2272 29
+11 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+12 wlan0 0x0 1000 0 6126 13 2013 16 5934 11 192 2 0 0 1821 14 192 2 0 0
+13 wlan0 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+14 wlan0 0x0 10013 0 0 0 144 2 0 0 0 0 0 0 144 2 0 0 0 0
+15 wlan0 0x0 10013 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+16 wlan0 0x0 10018 0 5980263 4715 167667 1922 5972583 4709 0 0 7680 6 167667 1922 0 0 0 0
+17 wlan0 0x0 10018 1 43995 37 2766 27 43995 37 0 0 0 0 2766 27 0 0 0 0
+18 wlan0 0x0 10060 0 134356 133 8705 74 134356 133 0 0 0 0 8705 74 0 0 0 0
+19 wlan0 0x0 10060 1 294709 326 26448 256 294709 326 0 0 0 0 26448 256 0 0 0 0
+20 wlan0 0x0 10079 0 10926 13 1507 13 10926 13 0 0 0 0 1507 13 0 0 0 0
+21 wlan0 0x0 10079 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+22 wlan0 0x0 10102 0 25038 42 8245 57 25038 42 0 0 0 0 8245 57 0 0 0 0
+23 wlan0 0x0 10102 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+24 wlan0 0x0 10103 0 0 0 192 2 0 0 0 0 0 0 0 0 192 2 0 0
+25 wlan0 0x0 10103 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+26 wlan0 0x1000040700000000 10018 0 831 6 655 5 831 6 0 0 0 0 655 5 0 0 0 0
+27 wlan0 0x1000040700000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+28 wlan0 0x1000040b00000000 10018 0 1714 8 1561 7 1714 8 0 0 0 0 1561 7 0 0 0 0
+29 wlan0 0x1000040b00000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+30 wlan0 0x1000120300000000 10018 0 8243 11 2234 12 8243 11 0 0 0 0 2234 12 0 0 0 0
+31 wlan0 0x1000120300000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+32 wlan0 0x1000180300000000 10018 0 56368 49 4790 39 56368 49 0 0 0 0 4790 39 0 0 0 0
+33 wlan0 0x1000180300000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+34 wlan0 0x1000300000000000 10018 0 9488 17 18813 25 1808 11 0 0 7680 6 18813 25 0 0 0 0
+35 wlan0 0x1000300000000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+36 wlan0 0x3000180400000000 10018 0 131262 103 7416 103 131262 103 0 0 0 0 7416 103 0 0 0 0
+37 wlan0 0x3000180400000000 10018 1 43995 37 2766 27 43995 37 0 0 0 0 2766 27 0 0 0 0
+38 wlan0 0xffffff0100000000 10018 0 5771986 4518 131190 1725 5771986 4518 0 0 0 0 131190 1725 0 0 0 0
+39 wlan0 0xffffff0100000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+40 dummy0 0x0 0 0 0 0 168 3 0 0 0 0 0 0 0 0 0 0 168 3
+41 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+42 lo 0x0 0 0 1288 16 1288 16 0 0 532 8 756 8 0 0 532 8 756 8
+43 lo 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after
new file mode 100644
index 0000000..c78f84f
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after
@@ -0,0 +1,187 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 r_rmnet_data0 0x0 0 0 0 0 392 6 0 0 0 0 0 0 0 0 0 0 392 6
+3 r_rmnet_data0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 v4-wlan0 0x0 0 0 58952 2072 2888 65 264 6 0 0 58688 2066 132 3 0 0 2756 62
+5 v4-wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+6 v4-wlan0 0x0 10034 0 6192 11 1445 11 6192 11 0 0 0 0 1445 11 0 0 0 0
+7 v4-wlan0 0x0 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+8 v4-wlan0 0x0 10057 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+9 v4-wlan0 0x0 10057 1 728 7 392 7 0 0 728 7 0 0 0 0 392 7 0 0
+10 v4-wlan0 0x0 10106 0 2232 18 2232 18 0 0 2232 18 0 0 0 0 2232 18 0 0
+11 v4-wlan0 0x0 10106 1 432952718 314238 5442288 121260 432950238 314218 2480 20 0 0 5433900 121029 8388 231 0 0
+12 wlan0 0x0 0 0 440746376 329772 8524052 130894 439660007 315369 232001 1276 854368 13127 7871216 121284 108568 1325 544268 8285
+13 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+14 wlan0 0x0 1000 0 77113 272 56151 575 77113 272 0 0 0 0 19191 190 36960 385 0 0
+15 wlan0 0x0 1000 1 20227 80 8356 72 18539 74 1688 6 0 0 7562 66 794 6 0 0
+16 wlan0 0x0 10006 0 80755 92 9122 99 80755 92 0 0 0 0 9122 99 0 0 0 0
+17 wlan0 0x0 10006 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+18 wlan0 0x0 10015 0 4390 7 14824 252 4390 7 0 0 0 0 14824 252 0 0 0 0
+19 wlan0 0x0 10015 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+20 wlan0 0x0 10018 0 4928 11 1741 14 4928 11 0 0 0 0 1741 14 0 0 0 0
+21 wlan0 0x0 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+22 wlan0 0x0 10020 0 21163552 34395 2351650 15326 21162947 34390 605 5 0 0 2351045 15321 605 5 0 0
+23 wlan0 0x0 10020 1 13835740 12938 1548795 6365 13833754 12920 1986 18 0 0 1546809 6347 1986 18 0 0
+24 wlan0 0x0 10023 0 13405 40 5042 44 13405 40 0 0 0 0 5042 44 0 0 0 0
+25 wlan0 0x0 10023 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+26 wlan0 0x0 10034 0 436394741 342648 6237981 80442 436394741 342648 0 0 0 0 6237981 80442 0 0 0 0
+27 wlan0 0x0 10034 1 64860872 51297 1335539 15546 64860872 51297 0 0 0 0 1335539 15546 0 0 0 0
+28 wlan0 0x0 10044 0 17614444 14774 521004 5694 17329882 14432 284562 342 0 0 419974 5408 101030 286 0 0
+29 wlan0 0x0 10044 1 17701 33 3100 28 17701 33 0 0 0 0 3100 28 0 0 0 0
+30 wlan0 0x0 10057 0 12312074 9339 436098 5450 12248060 9263 64014 76 0 0 414224 5388 21874 62 0 0
+31 wlan0 0x0 10057 1 1332953195 954797 31849632 457698 1331933207 953569 1019988 1228 0 0 31702284 456899 147348 799 0 0
+32 wlan0 0x0 10060 0 32972 200 433705 380 32972 200 0 0 0 0 433705 380 0 0 0 0
+33 wlan0 0x0 10060 1 32106 66 37789 87 32106 66 0 0 0 0 37789 87 0 0 0 0
+34 wlan0 0x0 10061 0 7675 23 2509 22 7675 23 0 0 0 0 2509 22 0 0 0 0
+35 wlan0 0x0 10061 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+36 wlan0 0x0 10074 0 38355 82 10447 97 38355 82 0 0 0 0 10447 97 0 0 0 0
+37 wlan0 0x0 10074 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+38 wlan0 0x0 10078 0 49013 79 7167 69 49013 79 0 0 0 0 7167 69 0 0 0 0
+39 wlan0 0x0 10078 1 5872 8 1236 10 5872 8 0 0 0 0 1236 10 0 0 0 0
+40 wlan0 0x0 10082 0 8301 13 1981 15 8301 13 0 0 0 0 1981 15 0 0 0 0
+41 wlan0 0x0 10082 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+42 wlan0 0x0 10086 0 7001 14 1579 15 7001 14 0 0 0 0 1579 15 0 0 0 0
+43 wlan0 0x0 10086 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+44 wlan0 0x0 10090 0 24327795 20224 920502 14661 24327795 20224 0 0 0 0 920502 14661 0 0 0 0
+45 wlan0 0x0 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+46 wlan0 0x0 10092 0 36849 78 12449 81 36849 78 0 0 0 0 12449 81 0 0 0 0
+47 wlan0 0x0 10092 1 60 1 103 1 60 1 0 0 0 0 103 1 0 0 0 0
+48 wlan0 0x0 10095 0 131962 223 37069 241 131962 223 0 0 0 0 37069 241 0 0 0 0
+49 wlan0 0x0 10095 1 12949 21 3930 21 12949 21 0 0 0 0 3930 21 0 0 0 0
+50 wlan0 0x0 10106 0 30899554 22679 632476 12296 30895334 22645 4220 34 0 0 628256 12262 4220 34 0 0
+51 wlan0 0x0 10106 1 88923475 64963 1606962 35612 88917201 64886 3586 29 2688 48 1602032 35535 4930 77 0 0
+52 wlan0 0x40700000000 10020 0 705732 10589 404428 5504 705732 10589 0 0 0 0 404428 5504 0 0 0 0
+53 wlan0 0x40700000000 10020 1 2376 36 1296 18 2376 36 0 0 0 0 1296 18 0 0 0 0
+54 wlan0 0x40800000000 10020 0 34624 146 122525 160 34624 146 0 0 0 0 122525 160 0 0 0 0
+55 wlan0 0x40800000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+56 wlan0 0x40b00000000 10020 0 22411 85 7364 57 22411 85 0 0 0 0 7364 57 0 0 0 0
+57 wlan0 0x40b00000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+58 wlan0 0x120300000000 10020 0 76641 241 32783 169 76641 241 0 0 0 0 32783 169 0 0 0 0
+59 wlan0 0x120300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+60 wlan0 0x130100000000 10020 0 73101 287 23236 203 73101 287 0 0 0 0 23236 203 0 0 0 0
+61 wlan0 0x130100000000 10020 1 264 4 144 2 264 4 0 0 0 0 144 2 0 0 0 0
+62 wlan0 0x180300000000 10020 0 330648 399 24736 232 330648 399 0 0 0 0 24736 232 0 0 0 0
+63 wlan0 0x180300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+64 wlan0 0x180400000000 10020 0 21865 59 5022 42 21865 59 0 0 0 0 5022 42 0 0 0 0
+65 wlan0 0x180400000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+66 wlan0 0x300000000000 10020 0 15984 65 26927 57 15984 65 0 0 0 0 26927 57 0 0 0 0
+67 wlan0 0x300000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+68 wlan0 0x1065fff00000000 10020 0 131871 599 93783 445 131871 599 0 0 0 0 93783 445 0 0 0 0
+69 wlan0 0x1065fff00000000 10020 1 264 4 144 2 264 4 0 0 0 0 144 2 0 0 0 0
+70 wlan0 0x1b24f4600000000 10034 0 15445 42 23329 45 15445 42 0 0 0 0 23329 45 0 0 0 0
+71 wlan0 0x1b24f4600000000 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+72 wlan0 0x1000010000000000 10020 0 5542 9 1364 10 5542 9 0 0 0 0 1364 10 0 0 0 0
+73 wlan0 0x1000010000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+74 wlan0 0x1000040100000000 10020 0 47196 184 213319 257 47196 184 0 0 0 0 213319 257 0 0 0 0
+75 wlan0 0x1000040100000000 10020 1 60 1 103 1 60 1 0 0 0 0 103 1 0 0 0 0
+76 wlan0 0x1000040700000000 10020 0 11599 50 10786 47 11599 50 0 0 0 0 10786 47 0 0 0 0
+77 wlan0 0x1000040700000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+78 wlan0 0x1000040800000000 10020 0 21902 145 174139 166 21902 145 0 0 0 0 174139 166 0 0 0 0
+79 wlan0 0x1000040800000000 10020 1 8568 88 105743 90 8568 88 0 0 0 0 105743 90 0 0 0 0
+80 wlan0 0x1000100300000000 10020 0 55213 118 194551 199 55213 118 0 0 0 0 194551 199 0 0 0 0
+81 wlan0 0x1000100300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+82 wlan0 0x1000120300000000 10020 0 50826 74 21153 70 50826 74 0 0 0 0 21153 70 0 0 0 0
+83 wlan0 0x1000120300000000 10020 1 72 1 175 2 72 1 0 0 0 0 175 2 0 0 0 0
+84 wlan0 0x1000180300000000 10020 0 744198 657 65437 592 744198 657 0 0 0 0 65437 592 0 0 0 0
+85 wlan0 0x1000180300000000 10020 1 144719 132 10989 108 144719 132 0 0 0 0 10989 108 0 0 0 0
+86 wlan0 0x1000180600000000 10020 0 4599 8 1928 10 4599 8 0 0 0 0 1928 10 0 0 0 0
+87 wlan0 0x1000180600000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+88 wlan0 0x1000250000000000 10020 0 57740 98 13076 88 57740 98 0 0 0 0 13076 88 0 0 0 0
+89 wlan0 0x1000250000000000 10020 1 328 3 414 4 207 2 121 1 0 0 293 3 121 1 0 0
+90 wlan0 0x1000300000000000 10020 0 7675 30 31331 32 7675 30 0 0 0 0 31331 32 0 0 0 0
+91 wlan0 0x1000300000000000 10020 1 30173 97 101335 100 30173 97 0 0 0 0 101335 100 0 0 0 0
+92 wlan0 0x1000310200000000 10020 0 1681 9 2194 9 1681 9 0 0 0 0 2194 9 0 0 0 0
+93 wlan0 0x1000310200000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+94 wlan0 0x1000360000000000 10020 0 5606 20 2831 20 5606 20 0 0 0 0 2831 20 0 0 0 0
+95 wlan0 0x1000360000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+96 wlan0 0x11065fff00000000 10020 0 18363 91 83367 104 18363 91 0 0 0 0 83367 104 0 0 0 0
+97 wlan0 0x11065fff00000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+98 wlan0 0x3000009600000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+99 wlan0 0x3000009600000000 10020 1 6163 18 2424 18 6163 18 0 0 0 0 2424 18 0 0 0 0
+100 wlan0 0x3000009800000000 10020 0 23337 46 8723 39 23337 46 0 0 0 0 8723 39 0 0 0 0
+101 wlan0 0x3000009800000000 10020 1 33744 93 72437 89 33744 93 0 0 0 0 72437 89 0 0 0 0
+102 wlan0 0x3000020000000000 10020 0 4124 11 8969 19 4124 11 0 0 0 0 8969 19 0 0 0 0
+103 wlan0 0x3000020000000000 10020 1 5993 11 3815 14 5993 11 0 0 0 0 3815 14 0 0 0 0
+104 wlan0 0x3000040100000000 10020 0 113809 342 135666 308 113809 342 0 0 0 0 135666 308 0 0 0 0
+105 wlan0 0x3000040100000000 10020 1 142508 642 500579 637 142508 642 0 0 0 0 500579 637 0 0 0 0
+106 wlan0 0x3000040700000000 10020 0 365815 5119 213340 2733 365815 5119 0 0 0 0 213340 2733 0 0 0 0
+107 wlan0 0x3000040700000000 10020 1 30747 130 18408 100 30747 130 0 0 0 0 18408 100 0 0 0 0
+108 wlan0 0x3000040800000000 10020 0 34672 112 68623 92 34672 112 0 0 0 0 68623 92 0 0 0 0
+109 wlan0 0x3000040800000000 10020 1 78443 199 140944 192 78443 199 0 0 0 0 140944 192 0 0 0 0
+110 wlan0 0x3000040b00000000 10020 0 14949 33 4017 26 14949 33 0 0 0 0 4017 26 0 0 0 0
+111 wlan0 0x3000040b00000000 10020 1 996 15 576 8 996 15 0 0 0 0 576 8 0 0 0 0
+112 wlan0 0x3000090000000000 10020 0 11826 67 7309 52 11826 67 0 0 0 0 7309 52 0 0 0 0
+113 wlan0 0x3000090000000000 10020 1 24805 41 4785 41 24805 41 0 0 0 0 4785 41 0 0 0 0
+114 wlan0 0x3000100300000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+115 wlan0 0x3000100300000000 10020 1 3112 10 1628 10 3112 10 0 0 0 0 1628 10 0 0 0 0
+116 wlan0 0x3000120300000000 10020 0 38249 107 20374 85 38249 107 0 0 0 0 20374 85 0 0 0 0
+117 wlan0 0x3000120300000000 10020 1 122581 174 36792 143 122581 174 0 0 0 0 36792 143 0 0 0 0
+118 wlan0 0x3000130100000000 10020 0 2700 41 1524 21 2700 41 0 0 0 0 1524 21 0 0 0 0
+119 wlan0 0x3000130100000000 10020 1 22515 59 8366 52 22515 59 0 0 0 0 8366 52 0 0 0 0
+120 wlan0 0x3000180200000000 10020 0 6411 18 14511 20 6411 18 0 0 0 0 14511 20 0 0 0 0
+121 wlan0 0x3000180200000000 10020 1 336 5 319 4 336 5 0 0 0 0 319 4 0 0 0 0
+122 wlan0 0x3000180300000000 10020 0 129301 136 17622 97 129301 136 0 0 0 0 17622 97 0 0 0 0
+123 wlan0 0x3000180300000000 10020 1 464787 429 41703 336 464787 429 0 0 0 0 41703 336 0 0 0 0
+124 wlan0 0x3000180400000000 10020 0 11014 39 2787 25 11014 39 0 0 0 0 2787 25 0 0 0 0
+125 wlan0 0x3000180400000000 10020 1 144040 139 7540 80 144040 139 0 0 0 0 7540 80 0 0 0 0
+126 wlan0 0x3000210100000000 10020 0 10278 44 4579 33 10278 44 0 0 0 0 4579 33 0 0 0 0
+127 wlan0 0x3000210100000000 10020 1 31151 73 14159 47 31151 73 0 0 0 0 14159 47 0 0 0 0
+128 wlan0 0x3000250000000000 10020 0 132 2 72 1 132 2 0 0 0 0 72 1 0 0 0 0
+129 wlan0 0x3000250000000000 10020 1 76614 143 17711 130 76080 137 534 6 0 0 17177 124 534 6 0 0
+130 wlan0 0x3000260100000000 10020 0 9426 26 3535 20 9426 26 0 0 0 0 3535 20 0 0 0 0
+131 wlan0 0x3000260100000000 10020 1 468 7 288 4 468 7 0 0 0 0 288 4 0 0 0 0
+132 wlan0 0x3000300000000000 10020 0 7241 29 12055 26 7241 29 0 0 0 0 12055 26 0 0 0 0
+133 wlan0 0x3000300000000000 10020 1 3273 23 11232 21 3273 23 0 0 0 0 11232 21 0 0 0 0
+134 wlan0 0x3000310000000000 10020 0 132 2 72 1 132 2 0 0 0 0 72 1 0 0 0 0
+135 wlan0 0x3000310000000000 10020 1 53425 64 8721 62 53425 64 0 0 0 0 8721 62 0 0 0 0
+136 wlan0 0x3000310500000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+137 wlan0 0x3000310500000000 10020 1 9929 16 3879 18 9929 16 0 0 0 0 3879 18 0 0 0 0
+138 wlan0 0x3000320100000000 10020 0 6844 14 3745 13 6844 14 0 0 0 0 3745 13 0 0 0 0
+139 wlan0 0x3000320100000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+140 wlan0 0x3000360000000000 10020 0 8855 43 4749 31 8855 43 0 0 0 0 4749 31 0 0 0 0
+141 wlan0 0x3000360000000000 10020 1 5597 19 2456 19 5597 19 0 0 0 0 2456 19 0 0 0 0
+142 wlan0 0x3010000000000000 10090 0 605140 527 38435 429 605140 527 0 0 0 0 38435 429 0 0 0 0
+143 wlan0 0x3010000000000000 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+144 wlan0 0x31065fff00000000 10020 0 22011 67 29665 64 22011 67 0 0 0 0 29665 64 0 0 0 0
+145 wlan0 0x31065fff00000000 10020 1 10695 34 18347 35 10695 34 0 0 0 0 18347 35 0 0 0 0
+146 wlan0 0x32e544f900000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+147 wlan0 0x32e544f900000000 10034 1 40143 54 7299 61 40143 54 0 0 0 0 7299 61 0 0 0 0
+148 wlan0 0x58872a4400000000 10018 0 4928 11 1669 13 4928 11 0 0 0 0 1669 13 0 0 0 0
+149 wlan0 0x58872a4400000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+150 wlan0 0x5caeaa7b00000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+151 wlan0 0x5caeaa7b00000000 10034 1 74971 73 7103 75 74971 73 0 0 0 0 7103 75 0 0 0 0
+152 wlan0 0x9e00923800000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+153 wlan0 0x9e00923800000000 10034 1 72385 98 13072 110 72385 98 0 0 0 0 13072 110 0 0 0 0
+154 wlan0 0xb972bdd400000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+155 wlan0 0xb972bdd400000000 10034 1 15282 24 3034 27 15282 24 0 0 0 0 3034 27 0 0 0 0
+156 wlan0 0xc7c9f7ba00000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+157 wlan0 0xc7c9f7ba00000000 10034 1 194915 185 13316 138 194915 185 0 0 0 0 13316 138 0 0 0 0
+158 wlan0 0xc9395b2600000000 10034 0 6991 13 6215 14 6991 13 0 0 0 0 6215 14 0 0 0 0
+159 wlan0 0xc9395b2600000000 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+160 wlan0 0xdaddf21100000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+161 wlan0 0xdaddf21100000000 10034 1 928676 849 81570 799 928676 849 0 0 0 0 81570 799 0 0 0 0
+162 wlan0 0xe8d195d100000000 10020 0 516 8 288 4 516 8 0 0 0 0 288 4 0 0 0 0
+163 wlan0 0xe8d195d100000000 10020 1 5905 15 2622 15 5905 15 0 0 0 0 2622 15 0 0 0 0
+164 wlan0 0xe8d195d100000000 10034 0 236640 524 312523 555 236640 524 0 0 0 0 312523 555 0 0 0 0
+165 wlan0 0xe8d195d100000000 10034 1 319028 539 188776 553 319028 539 0 0 0 0 188776 553 0 0 0 0
+166 wlan0 0xffffff0100000000 10006 0 80755 92 9122 99 80755 92 0 0 0 0 9122 99 0 0 0 0
+167 wlan0 0xffffff0100000000 10006 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+168 wlan0 0xffffff0100000000 10020 0 17874405 14068 223987 3065 17874405 14068 0 0 0 0 223987 3065 0 0 0 0
+169 wlan0 0xffffff0100000000 10020 1 11011258 8672 177693 2407 11011258 8672 0 0 0 0 177693 2407 0 0 0 0
+170 wlan0 0xffffff0100000000 10034 0 436062595 341880 5843990 79630 436062595 341880 0 0 0 0 5843990 79630 0 0 0 0
+171 wlan0 0xffffff0100000000 10034 1 63201220 49447 1005882 13713 63201220 49447 0 0 0 0 1005882 13713 0 0 0 0
+172 wlan0 0xffffff0100000000 10044 0 17159287 13702 356212 4778 17159287 13702 0 0 0 0 356212 4778 0 0 0 0
+173 wlan0 0xffffff0100000000 10044 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+174 wlan0 0xffffff0100000000 10078 0 10439 17 1665 15 10439 17 0 0 0 0 1665 15 0 0 0 0
+175 wlan0 0xffffff0100000000 10078 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+176 wlan0 0xffffff0100000000 10090 0 23722655 19697 881995 14231 23722655 19697 0 0 0 0 881995 14231 0 0 0 0
+177 wlan0 0xffffff0100000000 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+178 wlan0 0xffffff0500000000 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+179 wlan0 0xffffff0500000000 1000 1 1592 5 314 1 0 0 1592 5 0 0 0 0 314 1 0 0
+180 wlan0 0xffffff0600000000 1000 0 0 0 36960 385 0 0 0 0 0 0 0 0 36960 385 0 0
+181 wlan0 0xffffff0600000000 1000 1 96 1 480 5 0 0 96 1 0 0 0 0 480 5 0 0
+182 wlan0 0xffffff0700000000 1000 0 38732 229 16567 163 38732 229 0 0 0 0 16567 163 0 0 0 0
+183 wlan0 0xffffff0700000000 1000 1 18539 74 7562 66 18539 74 0 0 0 0 7562 66 0 0 0 0
+184 wlan0 0xffffff0900000000 1000 0 38381 43 2624 27 38381 43 0 0 0 0 2624 27 0 0 0 0
+185 wlan0 0xffffff0900000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+186 dummy0 0x0 0 0 0 0 168 3 0 0 0 0 0 0 0 0 0 0 168 3
+187 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before
new file mode 100644
index 0000000..d035387
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before
@@ -0,0 +1,185 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 r_rmnet_data0 0x0 0 0 0 0 392 6 0 0 0 0 0 0 0 0 0 0 392 6
+3 r_rmnet_data0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 v4-wlan0 0x0 0 0 58848 2070 2836 64 160 4 0 0 58688 2066 80 2 0 0 2756 62
+5 v4-wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+6 v4-wlan0 0x0 10034 0 6192 11 1445 11 6192 11 0 0 0 0 1445 11 0 0 0 0
+7 v4-wlan0 0x0 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+8 v4-wlan0 0x0 10057 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+9 v4-wlan0 0x0 10057 1 728 7 392 7 0 0 728 7 0 0 0 0 392 7 0 0
+10 v4-wlan0 0x0 10106 0 1488 12 1488 12 0 0 1488 12 0 0 0 0 1488 12 0 0
+11 v4-wlan0 0x0 10106 1 323981189 235142 3509032 84542 323979453 235128 1736 14 0 0 3502676 84363 6356 179 0 0
+12 wlan0 0x0 0 0 330187296 250652 5855801 94173 329106990 236273 226202 1255 854104 13124 5208040 84634 103637 1256 544124 8283
+13 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+14 wlan0 0x0 1000 0 77113 272 56151 575 77113 272 0 0 0 0 19191 190 36960 385 0 0
+15 wlan0 0x0 1000 1 20227 80 8356 72 18539 74 1688 6 0 0 7562 66 794 6 0 0
+16 wlan0 0x0 10006 0 80755 92 9122 99 80755 92 0 0 0 0 9122 99 0 0 0 0
+17 wlan0 0x0 10006 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+18 wlan0 0x0 10015 0 4390 7 14824 252 4390 7 0 0 0 0 14824 252 0 0 0 0
+19 wlan0 0x0 10015 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+20 wlan0 0x0 10018 0 4928 11 1741 14 4928 11 0 0 0 0 1741 14 0 0 0 0
+21 wlan0 0x0 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+22 wlan0 0x0 10020 0 21141412 34316 2329881 15262 21140807 34311 605 5 0 0 2329276 15257 605 5 0 0
+23 wlan0 0x0 10020 1 13835740 12938 1548555 6362 13833754 12920 1986 18 0 0 1546569 6344 1986 18 0 0
+24 wlan0 0x0 10023 0 13405 40 5042 44 13405 40 0 0 0 0 5042 44 0 0 0 0
+25 wlan0 0x0 10023 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+26 wlan0 0x0 10034 0 436394741 342648 6237981 80442 436394741 342648 0 0 0 0 6237981 80442 0 0 0 0
+27 wlan0 0x0 10034 1 64860872 51297 1335539 15546 64860872 51297 0 0 0 0 1335539 15546 0 0 0 0
+28 wlan0 0x0 10044 0 17614444 14774 521004 5694 17329882 14432 284562 342 0 0 419974 5408 101030 286 0 0
+29 wlan0 0x0 10044 1 17701 33 3100 28 17701 33 0 0 0 0 3100 28 0 0 0 0
+30 wlan0 0x0 10057 0 12311735 9335 435954 5448 12247721 9259 64014 76 0 0 414080 5386 21874 62 0 0
+31 wlan0 0x0 10057 1 1332953195 954797 31849632 457698 1331933207 953569 1019988 1228 0 0 31702284 456899 147348 799 0 0
+32 wlan0 0x0 10060 0 32972 200 433705 380 32972 200 0 0 0 0 433705 380 0 0 0 0
+33 wlan0 0x0 10060 1 32106 66 37789 87 32106 66 0 0 0 0 37789 87 0 0 0 0
+34 wlan0 0x0 10061 0 7675 23 2509 22 7675 23 0 0 0 0 2509 22 0 0 0 0
+35 wlan0 0x0 10061 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+36 wlan0 0x0 10074 0 38355 82 10447 97 38355 82 0 0 0 0 10447 97 0 0 0 0
+37 wlan0 0x0 10074 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+38 wlan0 0x0 10078 0 49013 79 7167 69 49013 79 0 0 0 0 7167 69 0 0 0 0
+39 wlan0 0x0 10078 1 5872 8 1236 10 5872 8 0 0 0 0 1236 10 0 0 0 0
+40 wlan0 0x0 10082 0 8301 13 1981 15 8301 13 0 0 0 0 1981 15 0 0 0 0
+41 wlan0 0x0 10082 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+42 wlan0 0x0 10086 0 7001 14 1579 15 7001 14 0 0 0 0 1579 15 0 0 0 0
+43 wlan0 0x0 10086 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+44 wlan0 0x0 10090 0 24327795 20224 920502 14661 24327795 20224 0 0 0 0 920502 14661 0 0 0 0
+45 wlan0 0x0 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+46 wlan0 0x0 10092 0 36849 78 12449 81 36849 78 0 0 0 0 12449 81 0 0 0 0
+47 wlan0 0x0 10092 1 60 1 103 1 60 1 0 0 0 0 103 1 0 0 0 0
+48 wlan0 0x0 10095 0 131962 223 37069 241 131962 223 0 0 0 0 37069 241 0 0 0 0
+49 wlan0 0x0 10095 1 12949 21 3930 21 12949 21 0 0 0 0 3930 21 0 0 0 0
+50 wlan0 0x0 10106 0 30899554 22679 632476 12296 30895334 22645 4220 34 0 0 628256 12262 4220 34 0 0
+51 wlan0 0x0 10106 1 88922349 64952 1605126 35599 88916075 64875 3586 29 2688 48 1600196 35522 4930 77 0 0
+52 wlan0 0x40700000000 10020 0 705732 10589 404428 5504 705732 10589 0 0 0 0 404428 5504 0 0 0 0
+53 wlan0 0x40700000000 10020 1 2376 36 1296 18 2376 36 0 0 0 0 1296 18 0 0 0 0
+54 wlan0 0x40800000000 10020 0 34624 146 122525 160 34624 146 0 0 0 0 122525 160 0 0 0 0
+55 wlan0 0x40800000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+56 wlan0 0x40b00000000 10020 0 22411 85 7364 57 22411 85 0 0 0 0 7364 57 0 0 0 0
+57 wlan0 0x40b00000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+58 wlan0 0x120300000000 10020 0 76641 241 32783 169 76641 241 0 0 0 0 32783 169 0 0 0 0
+59 wlan0 0x120300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+60 wlan0 0x130100000000 10020 0 73101 287 23236 203 73101 287 0 0 0 0 23236 203 0 0 0 0
+61 wlan0 0x130100000000 10020 1 264 4 144 2 264 4 0 0 0 0 144 2 0 0 0 0
+62 wlan0 0x180300000000 10020 0 330648 399 24736 232 330648 399 0 0 0 0 24736 232 0 0 0 0
+63 wlan0 0x180300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+64 wlan0 0x180400000000 10020 0 21865 59 5022 42 21865 59 0 0 0 0 5022 42 0 0 0 0
+65 wlan0 0x180400000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+66 wlan0 0x300000000000 10020 0 15984 65 26927 57 15984 65 0 0 0 0 26927 57 0 0 0 0
+67 wlan0 0x300000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+68 wlan0 0x1065fff00000000 10020 0 131871 599 93783 445 131871 599 0 0 0 0 93783 445 0 0 0 0
+69 wlan0 0x1065fff00000000 10020 1 264 4 144 2 264 4 0 0 0 0 144 2 0 0 0 0
+70 wlan0 0x1b24f4600000000 10034 0 15445 42 23329 45 15445 42 0 0 0 0 23329 45 0 0 0 0
+71 wlan0 0x1b24f4600000000 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+72 wlan0 0x1000010000000000 10020 0 5542 9 1364 10 5542 9 0 0 0 0 1364 10 0 0 0 0
+73 wlan0 0x1000010000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+74 wlan0 0x1000040100000000 10020 0 47196 184 213319 257 47196 184 0 0 0 0 213319 257 0 0 0 0
+75 wlan0 0x1000040100000000 10020 1 60 1 103 1 60 1 0 0 0 0 103 1 0 0 0 0
+76 wlan0 0x1000040700000000 10020 0 11599 50 10786 47 11599 50 0 0 0 0 10786 47 0 0 0 0
+77 wlan0 0x1000040700000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+78 wlan0 0x1000040800000000 10020 0 21902 145 174139 166 21902 145 0 0 0 0 174139 166 0 0 0 0
+79 wlan0 0x1000040800000000 10020 1 8568 88 105743 90 8568 88 0 0 0 0 105743 90 0 0 0 0
+80 wlan0 0x1000100300000000 10020 0 55213 118 194551 199 55213 118 0 0 0 0 194551 199 0 0 0 0
+81 wlan0 0x1000100300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+82 wlan0 0x1000120300000000 10020 0 50826 74 21153 70 50826 74 0 0 0 0 21153 70 0 0 0 0
+83 wlan0 0x1000120300000000 10020 1 72 1 175 2 72 1 0 0 0 0 175 2 0 0 0 0
+84 wlan0 0x1000180300000000 10020 0 744198 657 65437 592 744198 657 0 0 0 0 65437 592 0 0 0 0
+85 wlan0 0x1000180300000000 10020 1 144719 132 10989 108 144719 132 0 0 0 0 10989 108 0 0 0 0
+86 wlan0 0x1000180600000000 10020 0 4599 8 1928 10 4599 8 0 0 0 0 1928 10 0 0 0 0
+87 wlan0 0x1000180600000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+88 wlan0 0x1000250000000000 10020 0 57740 98 13076 88 57740 98 0 0 0 0 13076 88 0 0 0 0
+89 wlan0 0x1000250000000000 10020 1 328 3 414 4 207 2 121 1 0 0 293 3 121 1 0 0
+90 wlan0 0x1000300000000000 10020 0 7675 30 31331 32 7675 30 0 0 0 0 31331 32 0 0 0 0
+91 wlan0 0x1000300000000000 10020 1 30173 97 101335 100 30173 97 0 0 0 0 101335 100 0 0 0 0
+92 wlan0 0x1000310200000000 10020 0 1681 9 2194 9 1681 9 0 0 0 0 2194 9 0 0 0 0
+93 wlan0 0x1000310200000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+94 wlan0 0x1000360000000000 10020 0 5606 20 2831 20 5606 20 0 0 0 0 2831 20 0 0 0 0
+95 wlan0 0x1000360000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+96 wlan0 0x11065fff00000000 10020 0 18363 91 83367 104 18363 91 0 0 0 0 83367 104 0 0 0 0
+97 wlan0 0x11065fff00000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+98 wlan0 0x3000009600000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+99 wlan0 0x3000009600000000 10020 1 6163 18 2424 18 6163 18 0 0 0 0 2424 18 0 0 0 0
+100 wlan0 0x3000009800000000 10020 0 23337 46 8723 39 23337 46 0 0 0 0 8723 39 0 0 0 0
+101 wlan0 0x3000009800000000 10020 1 33744 93 72437 89 33744 93 0 0 0 0 72437 89 0 0 0 0
+102 wlan0 0x3000020000000000 10020 0 4124 11 8969 19 4124 11 0 0 0 0 8969 19 0 0 0 0
+103 wlan0 0x3000020000000000 10020 1 5993 11 3815 14 5993 11 0 0 0 0 3815 14 0 0 0 0
+104 wlan0 0x3000040100000000 10020 0 106718 322 121557 287 106718 322 0 0 0 0 121557 287 0 0 0 0
+105 wlan0 0x3000040100000000 10020 1 142508 642 500579 637 142508 642 0 0 0 0 500579 637 0 0 0 0
+106 wlan0 0x3000040700000000 10020 0 365419 5113 213124 2730 365419 5113 0 0 0 0 213124 2730 0 0 0 0
+107 wlan0 0x3000040700000000 10020 1 30747 130 18408 100 30747 130 0 0 0 0 18408 100 0 0 0 0
+108 wlan0 0x3000040800000000 10020 0 34672 112 68623 92 34672 112 0 0 0 0 68623 92 0 0 0 0
+109 wlan0 0x3000040800000000 10020 1 78443 199 140944 192 78443 199 0 0 0 0 140944 192 0 0 0 0
+110 wlan0 0x3000040b00000000 10020 0 14949 33 4017 26 14949 33 0 0 0 0 4017 26 0 0 0 0
+111 wlan0 0x3000040b00000000 10020 1 996 15 576 8 996 15 0 0 0 0 576 8 0 0 0 0
+112 wlan0 0x3000090000000000 10020 0 4017 28 3610 25 4017 28 0 0 0 0 3610 25 0 0 0 0
+113 wlan0 0x3000090000000000 10020 1 24805 41 4545 38 24805 41 0 0 0 0 4545 38 0 0 0 0
+114 wlan0 0x3000100300000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+115 wlan0 0x3000100300000000 10020 1 3112 10 1628 10 3112 10 0 0 0 0 1628 10 0 0 0 0
+116 wlan0 0x3000120300000000 10020 0 38249 107 20374 85 38249 107 0 0 0 0 20374 85 0 0 0 0
+117 wlan0 0x3000120300000000 10020 1 122581 174 36792 143 122581 174 0 0 0 0 36792 143 0 0 0 0
+118 wlan0 0x3000130100000000 10020 0 2700 41 1524 21 2700 41 0 0 0 0 1524 21 0 0 0 0
+119 wlan0 0x3000130100000000 10020 1 22515 59 8366 52 22515 59 0 0 0 0 8366 52 0 0 0 0
+120 wlan0 0x3000180200000000 10020 0 6411 18 14511 20 6411 18 0 0 0 0 14511 20 0 0 0 0
+121 wlan0 0x3000180200000000 10020 1 336 5 319 4 336 5 0 0 0 0 319 4 0 0 0 0
+122 wlan0 0x3000180300000000 10020 0 129301 136 17622 97 129301 136 0 0 0 0 17622 97 0 0 0 0
+123 wlan0 0x3000180300000000 10020 1 464787 429 41703 336 464787 429 0 0 0 0 41703 336 0 0 0 0
+124 wlan0 0x3000180400000000 10020 0 11014 39 2787 25 11014 39 0 0 0 0 2787 25 0 0 0 0
+125 wlan0 0x3000180400000000 10020 1 144040 139 7540 80 144040 139 0 0 0 0 7540 80 0 0 0 0
+126 wlan0 0x3000210100000000 10020 0 10278 44 4579 33 10278 44 0 0 0 0 4579 33 0 0 0 0
+127 wlan0 0x3000210100000000 10020 1 31151 73 14159 47 31151 73 0 0 0 0 14159 47 0 0 0 0
+128 wlan0 0x3000250000000000 10020 0 132 2 72 1 132 2 0 0 0 0 72 1 0 0 0 0
+129 wlan0 0x3000250000000000 10020 1 76614 143 17711 130 76080 137 534 6 0 0 17177 124 534 6 0 0
+130 wlan0 0x3000260100000000 10020 0 9426 26 3535 20 9426 26 0 0 0 0 3535 20 0 0 0 0
+131 wlan0 0x3000260100000000 10020 1 468 7 288 4 468 7 0 0 0 0 288 4 0 0 0 0
+132 wlan0 0x3000300000000000 10020 0 7241 29 12055 26 7241 29 0 0 0 0 12055 26 0 0 0 0
+133 wlan0 0x3000300000000000 10020 1 3273 23 11232 21 3273 23 0 0 0 0 11232 21 0 0 0 0
+134 wlan0 0x3000310000000000 10020 0 132 2 72 1 132 2 0 0 0 0 72 1 0 0 0 0
+135 wlan0 0x3000310000000000 10020 1 53425 64 8721 62 53425 64 0 0 0 0 8721 62 0 0 0 0
+136 wlan0 0x3000310500000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+137 wlan0 0x3000310500000000 10020 1 9929 16 3879 18 9929 16 0 0 0 0 3879 18 0 0 0 0
+138 wlan0 0x3000360000000000 10020 0 8855 43 4749 31 8855 43 0 0 0 0 4749 31 0 0 0 0
+139 wlan0 0x3000360000000000 10020 1 5597 19 2456 19 5597 19 0 0 0 0 2456 19 0 0 0 0
+140 wlan0 0x3010000000000000 10090 0 605140 527 38435 429 605140 527 0 0 0 0 38435 429 0 0 0 0
+141 wlan0 0x3010000000000000 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+142 wlan0 0x31065fff00000000 10020 0 22011 67 29665 64 22011 67 0 0 0 0 29665 64 0 0 0 0
+143 wlan0 0x31065fff00000000 10020 1 10695 34 18347 35 10695 34 0 0 0 0 18347 35 0 0 0 0
+144 wlan0 0x32e544f900000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+145 wlan0 0x32e544f900000000 10034 1 40143 54 7299 61 40143 54 0 0 0 0 7299 61 0 0 0 0
+146 wlan0 0x58872a4400000000 10018 0 4928 11 1669 13 4928 11 0 0 0 0 1669 13 0 0 0 0
+147 wlan0 0x58872a4400000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+148 wlan0 0x5caeaa7b00000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+149 wlan0 0x5caeaa7b00000000 10034 1 74971 73 7103 75 74971 73 0 0 0 0 7103 75 0 0 0 0
+150 wlan0 0x9e00923800000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+151 wlan0 0x9e00923800000000 10034 1 72385 98 13072 110 72385 98 0 0 0 0 13072 110 0 0 0 0
+152 wlan0 0xb972bdd400000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+153 wlan0 0xb972bdd400000000 10034 1 15282 24 3034 27 15282 24 0 0 0 0 3034 27 0 0 0 0
+154 wlan0 0xc7c9f7ba00000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+155 wlan0 0xc7c9f7ba00000000 10034 1 194915 185 13316 138 194915 185 0 0 0 0 13316 138 0 0 0 0
+156 wlan0 0xc9395b2600000000 10034 0 6991 13 6215 14 6991 13 0 0 0 0 6215 14 0 0 0 0
+157 wlan0 0xc9395b2600000000 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+158 wlan0 0xdaddf21100000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+159 wlan0 0xdaddf21100000000 10034 1 928676 849 81570 799 928676 849 0 0 0 0 81570 799 0 0 0 0
+160 wlan0 0xe8d195d100000000 10020 0 516 8 288 4 516 8 0 0 0 0 288 4 0 0 0 0
+161 wlan0 0xe8d195d100000000 10020 1 5905 15 2622 15 5905 15 0 0 0 0 2622 15 0 0 0 0
+162 wlan0 0xe8d195d100000000 10034 0 236640 524 312523 555 236640 524 0 0 0 0 312523 555 0 0 0 0
+163 wlan0 0xe8d195d100000000 10034 1 319028 539 188776 553 319028 539 0 0 0 0 188776 553 0 0 0 0
+164 wlan0 0xffffff0100000000 10006 0 80755 92 9122 99 80755 92 0 0 0 0 9122 99 0 0 0 0
+165 wlan0 0xffffff0100000000 10006 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+166 wlan0 0xffffff0100000000 10020 0 17874405 14068 223987 3065 17874405 14068 0 0 0 0 223987 3065 0 0 0 0
+167 wlan0 0xffffff0100000000 10020 1 11011258 8672 177693 2407 11011258 8672 0 0 0 0 177693 2407 0 0 0 0
+168 wlan0 0xffffff0100000000 10034 0 436062595 341880 5843990 79630 436062595 341880 0 0 0 0 5843990 79630 0 0 0 0
+169 wlan0 0xffffff0100000000 10034 1 63201220 49447 1005882 13713 63201220 49447 0 0 0 0 1005882 13713 0 0 0 0
+170 wlan0 0xffffff0100000000 10044 0 17159287 13702 356212 4778 17159287 13702 0 0 0 0 356212 4778 0 0 0 0
+171 wlan0 0xffffff0100000000 10044 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+172 wlan0 0xffffff0100000000 10078 0 10439 17 1665 15 10439 17 0 0 0 0 1665 15 0 0 0 0
+173 wlan0 0xffffff0100000000 10078 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+174 wlan0 0xffffff0100000000 10090 0 23722655 19697 881995 14231 23722655 19697 0 0 0 0 881995 14231 0 0 0 0
+175 wlan0 0xffffff0100000000 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+176 wlan0 0xffffff0500000000 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+177 wlan0 0xffffff0500000000 1000 1 1592 5 314 1 0 0 1592 5 0 0 0 0 314 1 0 0
+178 wlan0 0xffffff0600000000 1000 0 0 0 36960 385 0 0 0 0 0 0 0 0 36960 385 0 0
+179 wlan0 0xffffff0600000000 1000 1 96 1 480 5 0 0 96 1 0 0 0 0 480 5 0 0
+180 wlan0 0xffffff0700000000 1000 0 38732 229 16567 163 38732 229 0 0 0 0 16567 163 0 0 0 0
+181 wlan0 0xffffff0700000000 1000 1 18539 74 7562 66 18539 74 0 0 0 0 7562 66 0 0 0 0
+182 wlan0 0xffffff0900000000 1000 0 38381 43 2624 27 38381 43 0 0 0 0 2624 27 0 0 0 0
+183 wlan0 0xffffff0900000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+184 dummy0 0x0 0 0 0 0 168 3 0 0 0 0 0 0 0 0 0 0 168 3
+185 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_simple b/tests/net/res/raw/xt_qtaguid_with_clat_simple
new file mode 100644
index 0000000..7f0e56f
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_simple
@@ -0,0 +1,5 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 v4-wlan0 0x0 10060 0 42600 213 4100 41 42600 213 4100 41 0 0 0 0 0 0 0 0
+3 v4-wlan0 0x0 10060 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 wlan0 0x0 0 0 46860 213 4920 41 46860 213 4920 41 0 0 0 0 0 0 0 0
+5 wlan0 0x0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index a84c306..e2ee603 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -802,7 +802,7 @@
     }
 
     struct SymbolComparator {
-        bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
+        bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) const {
             return a.symbol.name.value() < b.symbol.name.value();
         }
     };
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index bbf7f41..65c4eb8 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -1080,6 +1080,10 @@
         newRows[i] = image->rows[i + 1];
         memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
     }
+    delete[] image->rows[0];
+    if (H - 1 > 0) {
+      delete[] image->rows[H - 1];
+    }
     image->rows.swap(newRows);
 
     image->width -= 2;
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
index 23caaf8..4b760a7 100644
--- a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
@@ -50,12 +50,14 @@
             // the outline obtained is correct.
             child.setBackgroundBounds();
             ViewOutlineProvider outlineProvider = child.getOutlineProvider();
-            Outline outline = child.mAttachInfo.mTmpOutline;
-            outlineProvider.getOutline(child, outline);
-            if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
-                int restoreTo = transformCanvas(thisVG, canvas, child);
-                drawShadow(thisVG, canvas, child, outline);
-                canvas.restoreToCount(restoreTo);
+            if (outlineProvider != null) {
+                Outline outline = child.mAttachInfo.mTmpOutline;
+                outlineProvider.getOutline(child, outline);
+                if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
+                    int restoreTo = transformCanvas(thisVG, canvas, child);
+                    drawShadow(thisVG, canvas, child, outline);
+                    canvas.restoreToCount(restoreTo);
+                }
             }
         }
         return thisVG.drawChild_Original(canvas, child, drawingTime);
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
index 1b1f63e..6dc306e 100644
--- a/tools/obbtool/Android.mk
+++ b/tools/obbtool/Android.mk
@@ -39,7 +39,7 @@
 LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags
 LOCAL_SRC_FILES := pbkdf2gen.cpp
 LOCAL_LDLIBS += -ldl
-LOCAL_STATIC_LIBRARIES := libcrypto_static
+LOCAL_STATIC_LIBRARIES := libcrypto
 
 include $(BUILD_HOST_EXECUTABLE)
 
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 65b437d..ad9f9b8 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -125,6 +125,12 @@
 
     void setWifiApEnabled(in WifiConfiguration wifiConfig, boolean enable);
 
+    void updateInterfaceIpState(String ifaceName, int mode);
+
+    boolean startSoftAp(in WifiConfiguration wifiConfig);
+
+    boolean stopSoftAp();
+
     int getWifiApEnabledState();
 
     WifiConfiguration getWifiApConfiguration();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b79ecdd..b7fe9c7 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -16,10 +16,10 @@
 
 package android.net.wifi;
 
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
-import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.net.ConnectivityManager;
@@ -45,9 +45,9 @@
 import com.android.server.net.NetworkPinner;
 
 import java.net.InetAddress;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
-import java.util.Collections;
 
 /**
  * This class provides the primary API for managing all aspects of Wi-Fi
@@ -445,6 +445,35 @@
      *  @hide
      */
     public static final int SAP_START_FAILURE_NO_CHANNEL = 1;
+
+    /**
+     * Interface IP mode for configuration error.
+     *
+     * @see updateInterfaceIpState(String, int)
+     *
+     * @hide
+     */
+    public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0;
+
+    /**
+     * Interface IP mode for tethering.
+     *
+     * @see updateInterfaceIpState(String, int)
+     *
+     * @hide
+     */
+    public static final int IFACE_IP_MODE_TETHERED = 1;
+
+    /**
+     * Interface IP mode for Local Only Hotspot.
+     *
+     * @see updateInterfaceIpState(String, int)
+     *
+     * @hide
+     */
+    public static final int IFACE_IP_MODE_LOCAL_ONLY = 2;
+
+
     /**
      * Broadcast intent action indicating that a connection to the supplicant has
      * been established (and it is now possible
@@ -1717,6 +1746,59 @@
     }
 
     /**
+     * Call allowing ConnectivityService to update WifiService with interface mode changes.
+     *
+     * The possible modes include: {@link IFACE_IP_MODE_TETHERED},
+     *                             {@link IFACE_IP_MODE_LOCAL_ONLY},
+     *                             {@link IFACE_IP_MODE_CONFIGURATION_ERROR}
+     *
+     * @param ifaceName String name of the updated interface
+     * @param mode int representing the new mode
+     *
+     * @hide
+     */
+    public void updateInterfaceIpState(String ifaceName, int mode) {
+        try {
+            mService.updateInterfaceIpState(ifaceName, mode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Start SoftAp mode with the specified configuration.
+     * Note that starting in access point mode disables station
+     * mode operation
+     * @param wifiConfig SSID, security and channel details as
+     *        part of WifiConfiguration
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     *
+     * @hide
+     */
+    public boolean startSoftAp(@Nullable WifiConfiguration wifiConfig) {
+        try {
+            return mService.startSoftAp(wifiConfig);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stop SoftAp mode.
+     * Note that stopping softap mode will restore the previous wifi mode.
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     *
+     * @hide
+     */
+    public boolean stopSoftAp() {
+        try {
+            return mService.stopSoftAp();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the Wi-Fi enabled state.
      * @return One of {@link #WIFI_AP_STATE_DISABLED},
      *         {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index 82b3792..bf5c42b 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.NetworkSpecifier;
 import android.net.wifi.RttManager;
 import android.util.Log;
 
@@ -250,8 +251,8 @@
     }
 
     /**
-     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
-     * unencrypted WiFi Aware connection (link) to the specified peer. The
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+     * an unencrypted WiFi Aware connection (link) to the specified peer. The
      * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
      * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
      * <p>
@@ -276,13 +277,13 @@
      *                   request from only that peer. A RESPONDER may specify a {@code null} -
      *                   indicating that it will accept connection requests from any device.
      *
-     * @return A string to be used to construct
-     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+     * @return A {@link NetworkSpecifier} to be used to construct
+     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
      * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
-    public String createNetworkSpecifierOpen(@Nullable PeerHandle peerHandle) {
+    public NetworkSpecifier createNetworkSpecifierOpen(@Nullable PeerHandle peerHandle) {
         if (mTerminated) {
             Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session");
             return null;
@@ -302,8 +303,8 @@
     }
 
     /**
-     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
-     * encrypted WiFi Aware connection (link) to the specified peer. The
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+     * an encrypted WiFi Aware connection (link) to the specified peer. The
      * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
      * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
      * <p>
@@ -329,14 +330,14 @@
      *                   {@link #createNetworkSpecifierOpen(PeerHandle)} API to
      *                   specify an open (unencrypted) link.
      *
-     * @return A string to be used to construct
-     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+     * @return A {@link NetworkSpecifier} to be used to construct
+     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
      * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
-    public String createNetworkSpecifierPassphrase(@Nullable PeerHandle peerHandle,
-            @NonNull String passphrase) {
+    public NetworkSpecifier createNetworkSpecifierPassphrase(
+            @Nullable PeerHandle peerHandle, @NonNull String passphrase) {
         if (passphrase == null || passphrase.length() == 0) {
             throw new IllegalArgumentException("Passphrase must not be null or empty");
         }
@@ -361,8 +362,8 @@
     }
 
     /**
-     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
-     * encrypted WiFi Aware connection (link) to the specified peer. The
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+     * an encrypted WiFi Aware connection (link) to the specified peer. The
      * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
      * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
      * <p>
@@ -389,8 +390,8 @@
      *            Passphrase or {@link #createNetworkSpecifierOpen(PeerHandle)} to specify an
      *            open (unencrypted) link.
      *
-     * @return A string to be used to construct
-     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+     * @return A {@link NetworkSpecifier} to be used to construct
+     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
      * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
@@ -398,7 +399,7 @@
      * @hide
      */
     @SystemApi
-    public String createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle,
+    public NetworkSpecifier createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle,
             @NonNull byte[] pmk) {
         if (pmk == null || pmk.length == 0) {
             throw new IllegalArgumentException("PMK must not be null or empty");
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 4d3957a..b9ce61c 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -24,14 +24,15 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.wifi.RttManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
-import android.util.Base64;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -39,9 +40,6 @@
 
 import libcore.util.HexEncoding;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -129,65 +127,6 @@
     private static final boolean VDBG = false; // STOPSHIP if true
 
     /**
-     * Keys used to generate a Network Specifier for the Aware network request. The network
-     * specifier is formatted as a JSON string.
-     */
-
-    /**
-     * TYPE: in band, specific peer: role, client_id, session_id, peer_id, pmk/passphrase optional
-     * @hide
-     */
-    public static final int NETWORK_SPECIFIER_TYPE_IB = 0;
-
-    /**
-     * TYPE: in band, any peer: role, client_id, session_id, pmk/passphrase optional
-     * [only permitted for RESPONDER]
-     * @hide
-     */
-    public static final int NETWORK_SPECIFIER_TYPE_IB_ANY_PEER = 1;
-
-    /**
-     * TYPE: out-of-band: role, client_id, peer_mac, pmk/passphrase optional
-     * @hide
-     */
-    public static final int NETWORK_SPECIFIER_TYPE_OOB = 2;
-
-    /**
-     * TYPE: out-of-band, any peer: role, client_id, pmk/passphrase optional
-     * [only permitted for RESPONDER]
-     * @hide
-     */
-    public static final int NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER = 3;
-
-
-    /** @hide */
-    public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
-
-    /** @hide */
-    public static final String NETWORK_SPECIFIER_KEY_TYPE = "type";
-
-    /** @hide */
-    public static final String NETWORK_SPECIFIER_KEY_ROLE = "role";
-
-    /** @hide */
-    public static final String NETWORK_SPECIFIER_KEY_CLIENT_ID = "client_id";
-
-    /** @hide */
-    public static final String NETWORK_SPECIFIER_KEY_SESSION_ID = "session_id";
-
-    /** @hide */
-    public static final String NETWORK_SPECIFIER_KEY_PEER_ID = "peer_id";
-
-    /** @hide */
-    public static final String NETWORK_SPECIFIER_KEY_PEER_MAC = "peer_mac";
-
-    /** @hide */
-    public static final String NETWORK_SPECIFIER_KEY_PMK = "pmk";
-
-    /** @hide */
-    public static final String NETWORK_SPECIFIER_KEY_PASSPHRASE = "passphrase";
-
-    /**
      * Broadcast intent action to indicate that the state of Wi-Fi Aware availability has changed.
      * Use the {@link #isAvailable()} to query the current status.
      * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
@@ -483,7 +422,7 @@
     }
 
     /** @hide */
-    public String createNetworkSpecifier(int clientId, int role, int sessionId,
+    public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
             PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
         if (VDBG) {
             Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
@@ -492,9 +431,6 @@
                     + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
         }
 
-        int type = (peerHandle == null) ? NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
-                : NETWORK_SPECIFIER_TYPE_IB;
-
         if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
                 && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
             throw new IllegalArgumentException(
@@ -509,35 +445,21 @@
             }
         }
 
-        JSONObject json;
-        try {
-            json = new JSONObject();
-            json.put(NETWORK_SPECIFIER_KEY_TYPE, type);
-            json.put(NETWORK_SPECIFIER_KEY_ROLE, role);
-            json.put(NETWORK_SPECIFIER_KEY_CLIENT_ID, clientId);
-            json.put(NETWORK_SPECIFIER_KEY_SESSION_ID, sessionId);
-            if (peerHandle != null) {
-                json.put(NETWORK_SPECIFIER_KEY_PEER_ID, peerHandle.peerId);
-            }
-            if (pmk == null) {
-                pmk = new byte[0];
-            }
-            json.put(NETWORK_SPECIFIER_KEY_PMK,
-                    Base64.encodeToString(pmk, 0, pmk.length, Base64.DEFAULT));
-            if (passphrase == null) {
-                passphrase = new String();
-            }
-            json.put(NETWORK_SPECIFIER_KEY_PASSPHRASE, passphrase);
-
-        } catch (JSONException e) {
-            return "";
-        }
-
-        return json.toString();
+        return new WifiAwareNetworkSpecifier(
+                (peerHandle == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
+                        : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB,
+                role,
+                clientId,
+                sessionId,
+                peerHandle != null ? peerHandle.peerId : 0, // 0 is an invalid peer ID
+                null, // peerMac (not used in this method)
+                pmk,
+                passphrase,
+                Process.myUid());
     }
 
     /** @hide */
-    public String createNetworkSpecifier(int clientId, @DataPathRole int role,
+    public NetworkSpecifier createNetworkSpecifier(int clientId, @DataPathRole int role,
             @Nullable byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) {
         if (VDBG) {
             Log.v(TAG, "createNetworkSpecifier: role=" + role
@@ -545,9 +467,6 @@
                     + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
         }
 
-        int type = (peer == null) ?
-                NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER : NETWORK_SPECIFIER_TYPE_OOB;
-
         if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
                 && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
             throw new IllegalArgumentException(
@@ -564,29 +483,17 @@
             throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address");
         }
 
-        JSONObject json;
-        try {
-            json = new JSONObject();
-            json.put(NETWORK_SPECIFIER_KEY_TYPE, type);
-            json.put(NETWORK_SPECIFIER_KEY_ROLE, role);
-            json.put(NETWORK_SPECIFIER_KEY_CLIENT_ID, clientId);
-            if (peer != null) {
-                json.put(NETWORK_SPECIFIER_KEY_PEER_MAC, new String(HexEncoding.encode(peer)));
-            }
-            if (pmk == null) {
-                pmk = new byte[0];
-            }
-            json.put(NETWORK_SPECIFIER_KEY_PMK,
-                    Base64.encodeToString(pmk, 0, pmk.length, Base64.DEFAULT));
-            if (passphrase == null) {
-                passphrase = new String();
-            }
-            json.put(NETWORK_SPECIFIER_KEY_PASSPHRASE, passphrase);
-        } catch (JSONException e) {
-            return "";
-        }
-
-        return json.toString();
+        return new WifiAwareNetworkSpecifier(
+                (peer == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER
+                        : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
+                role,
+                clientId,
+                0, // 0 is an invalid session ID
+                0, // 0 is an invalid peer ID
+                peer,
+                pmk,
+                passphrase,
+                Process.myUid());
     }
 
     private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub {
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
new file mode 100644
index 0000000..e152f6c
--- /dev/null
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
@@ -0,0 +1,258 @@
+/*
+ * 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.wifi.aware;
+
+import android.net.NetworkSpecifier;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Network specifier object used to request a Wi-Fi Aware network. Apps do not create these objects
+ * directly but obtain them using
+ * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])} or
+ * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)} or their secure (Passphrase)
+ * versions.
+ *
+ * @hide
+ */
+public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+    /**
+     * TYPE: in band, specific peer: role, client_id, session_id, peer_id, pmk/passphrase optional
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_IB = 0;
+
+    /**
+     * TYPE: in band, any peer: role, client_id, session_id, pmk/passphrase optional
+     * [only permitted for RESPONDER]
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_IB_ANY_PEER = 1;
+
+    /**
+     * TYPE: out-of-band: role, client_id, peer_mac, pmk/passphrase optional
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_OOB = 2;
+
+    /**
+     * TYPE: out-of-band, any peer: role, client_id, pmk/passphrase optional
+     * [only permitted for RESPONDER]
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER = 3;
+
+    /** @hide */
+    public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
+
+    /**
+     * One of the NETWORK_SPECIFIER_TYPE_* constants. The type of the network specifier object.
+     * @hide
+     */
+    public final int type;
+
+    /**
+     * The role of the device: WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR or
+     * WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER.
+     * @hide
+     */
+    public final int role;
+
+    /**
+     * The client ID of the device.
+     * @hide
+     */
+    public final int clientId;
+
+    /**
+     * The session ID in which context to request a data-path. Only relevant for IB requests.
+     * @hide
+     */
+    public final int sessionId;
+
+    /**
+     * The peer ID of the device which the data-path should be connected to. Only relevant for
+     * IB requests (i.e. not IB_ANY_PEER or OOB*).
+     * @hide
+     */
+    public final int peerId;
+
+    /**
+     * The peer MAC address of the device which the data-path should be connected to. Only relevant
+     * for OB requests (i.e. not OOB_ANY_PEER or IB*).
+     * @hide
+     */
+    public final byte[] peerMac;
+
+    /**
+     * The PMK of the requested data-path. Can be null. Only one or none of pmk or passphrase should
+     * be specified.
+     * @hide
+     */
+    public final byte[] pmk;
+
+    /**
+     * The Passphrase of the requested data-path. Can be null. Only one or none of the pmk or
+     * passphrase should be specified.
+     * @hide
+     */
+    public final String passphrase;
+
+    /**
+     * The UID of the process initializing this network specifier. Validated by receiver using
+     * checkUidIfNecessary() and is used by satisfiedBy() to determine whether matches the
+     * offered network.
+     *
+     * @hide
+     */
+    public final int requestorUid;
+
+    /** @hide */
+    public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId,
+            byte[] peerMac, byte[] pmk, String passphrase, int requestorUid) {
+        this.type = type;
+        this.role = role;
+        this.clientId = clientId;
+        this.sessionId = sessionId;
+        this.peerId = peerId;
+        this.peerMac = peerMac;
+        this.pmk = pmk;
+        this.passphrase = passphrase;
+        this.requestorUid = requestorUid;
+    }
+
+    public static final Creator<WifiAwareNetworkSpecifier> CREATOR =
+            new Creator<WifiAwareNetworkSpecifier>() {
+                @Override
+                public WifiAwareNetworkSpecifier createFromParcel(Parcel in) {
+                    return new WifiAwareNetworkSpecifier(
+                        in.readInt(), // type
+                        in.readInt(), // role
+                        in.readInt(), // clientId
+                        in.readInt(), // sessionId
+                        in.readInt(), // peerId
+                        in.createByteArray(), // peerMac
+                        in.createByteArray(), // pmk
+                        in.readString(), // passphrase
+                        in.readInt()); // requestorUid
+                }
+
+                @Override
+                public WifiAwareNetworkSpecifier[] newArray(int size) {
+                    return new WifiAwareNetworkSpecifier[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(type);
+        dest.writeInt(role);
+        dest.writeInt(clientId);
+        dest.writeInt(sessionId);
+        dest.writeInt(peerId);
+        dest.writeByteArray(peerMac);
+        dest.writeByteArray(pmk);
+        dest.writeString(passphrase);
+        dest.writeInt(requestorUid);
+    }
+
+    /** @hide */
+    @Override
+    public boolean satisfiedBy(NetworkSpecifier other) {
+        // MatchAllNetworkSpecifier is taken care in NetworkCapabilities#satisfiedBySpecifier.
+        return equals(other);
+    }
+
+    /** @hide */
+    @Override
+    public int hashCode() {
+        int result = 17;
+
+        result = 31 * result + type;
+        result = 31 * result + role;
+        result = 31 * result + clientId;
+        result = 31 * result + sessionId;
+        result = 31 * result + peerId;
+        result = 31 * result + Arrays.hashCode(peerMac);
+        result = 31 * result + Arrays.hashCode(pmk);
+        result = 31 * result + Objects.hashCode(passphrase);
+        result = 31 * result + requestorUid;
+
+        return result;
+    }
+
+    /** @hide */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!(obj instanceof WifiAwareNetworkSpecifier)) {
+            return false;
+        }
+
+        WifiAwareNetworkSpecifier lhs = (WifiAwareNetworkSpecifier) obj;
+
+        return type == lhs.type
+                && role == lhs.role
+                && clientId == lhs.clientId
+                && sessionId == lhs.sessionId
+                && peerId == lhs.peerId
+                && Arrays.equals(peerMac, lhs.peerMac)
+                && Arrays.equals(pmk, lhs.pmk)
+                && Objects.equals(passphrase, lhs.passphrase)
+                && requestorUid == lhs.requestorUid;
+    }
+
+    /** @hide */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("WifiAwareNetworkSpecifier [");
+        sb.append("type=").append(type)
+                .append(", role=").append(role)
+                .append(", clientId=").append(clientId)
+                .append(", sessionId=").append(sessionId)
+                .append(", peerId=").append(peerId)
+                // masking potential PII (although low impact information)
+                .append(", peerMac=").append((peerMac == null) ? "<null>" : "<non-null>")
+                // masking PII
+                .append(", pmk=").append((pmk == null) ? "<null>" : "<non-null>")
+                // masking PII
+                .append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>")
+                .append(", requestorUid=").append(requestorUid)
+                .append("]");
+        return sb.toString();
+    }
+
+    /** @hide */
+    @Override
+    public void assertValidFromUid(int requestorUid) {
+        if (this.requestorUid != requestorUid) {
+            throw new SecurityException("mismatched UIDs");
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 895defb..ac3a6bb 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.NetworkSpecifier;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
@@ -184,8 +185,8 @@
     }
 
     /**
-     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
-     * unencrypted WiFi Aware connection (link) to the specified peer. The
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+     * an unencrypted WiFi Aware connection (link) to the specified peer. The
      * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
      * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
      * <p>
@@ -205,29 +206,29 @@
      *              peer. A RESPONDER may specify a {@code null} - indicating that it will accept
      *              connection requests from any device.
      *
-     * @return A string to be used to construct
-     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+     * @return A {@link NetworkSpecifier} to be used to construct
+     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
      * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
-    public String createNetworkSpecifierOpen(@WifiAwareManager.DataPathRole int role,
-            @Nullable byte[] peer) {
+    public NetworkSpecifier createNetworkSpecifierOpen(
+            @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer) {
         WifiAwareManager mgr = mMgr.get();
         if (mgr == null) {
             Log.e(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
-            return "";
+            return null;
         }
         if (mTerminated) {
             Log.e(TAG, "createNetworkSpecifierOpen: called after termination");
-            return "";
+            return null;
         }
         return mgr.createNetworkSpecifier(mClientId, role, peer, null, null);
     }
 
     /**
-     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
-     * encrypted WiFi Aware connection (link) to the specified peer. The
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+     * an encrypted WiFi Aware connection (link) to the specified peer. The
      * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
      * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
      * <p>
@@ -247,22 +248,23 @@
      *                   the passphrase. Use {@link #createNetworkSpecifierOpen(int, byte[])} to
      *                   specify an open (unencrypted) link.
      *
-     * @return A string to be used to construct
-     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+     * @return A {@link NetworkSpecifier} to be used to construct
+     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
      * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
-    public String createNetworkSpecifierPassphrase(@WifiAwareManager.DataPathRole int role,
-            @Nullable byte[] peer, @NonNull String passphrase) {
+    public NetworkSpecifier createNetworkSpecifierPassphrase(
+            @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer,
+            @NonNull String passphrase) {
         WifiAwareManager mgr = mMgr.get();
         if (mgr == null) {
             Log.e(TAG, "createNetworkSpecifierPassphrase: called post GC on WifiAwareManager");
-            return "";
+            return null;
         }
         if (mTerminated) {
             Log.e(TAG, "createNetworkSpecifierPassphrase: called after termination");
-            return "";
+            return null;
         }
         if (passphrase == null || passphrase.length() == 0) {
             throw new IllegalArgumentException("Passphrase must not be null or empty");
@@ -271,8 +273,8 @@
     }
 
     /**
-     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
-     * encrypted WiFi Aware connection (link) to the specified peer. The
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+     * an encrypted WiFi Aware connection (link) to the specified peer. The
      * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
      * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
      * <p>
@@ -294,8 +296,8 @@
      *            Passphrase or {@link #createNetworkSpecifierOpen(int, byte[])} to specify an
      *            open (unencrypted) link.
      *
-     * @return A string to be used to construct
-     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+     * @return A {@link NetworkSpecifier} to be used to construct
+     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
      * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
@@ -303,16 +305,16 @@
      * @hide
      */
     @SystemApi
-    public String createNetworkSpecifierPmk(@WifiAwareManager.DataPathRole int role,
-            @Nullable byte[] peer, @NonNull byte[] pmk) {
+    public NetworkSpecifier createNetworkSpecifierPmk(
+            @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer, @NonNull byte[] pmk) {
         WifiAwareManager mgr = mMgr.get();
         if (mgr == null) {
             Log.e(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
-            return "";
+            return null;
         }
         if (mTerminated) {
             Log.e(TAG, "createNetworkSpecifierPmk: called after termination");
-            return "";
+            return null;
         }
         if (pmk == null || pmk.length == 0) {
             throw new IllegalArgumentException("PMK must not be null or empty");
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 830db22..72a6a7a 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -34,11 +34,9 @@
 import android.os.Parcel;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Base64;
 
 import libcore.util.HexEncoding;
 
-import org.json.JSONObject;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -959,8 +957,6 @@
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
         final PublishConfig publishConfig = new PublishConfig.Builder().build();
 
-        String pmkB64 = Base64.encodeToString(pmk, Base64.DEFAULT);
-
         ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
                 WifiAwareSession.class);
         ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
@@ -991,51 +987,37 @@
         inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
 
         // (3) request an open (unencrypted) network specifier from the session
-        String networkSpecifier = publishSession.getValue().createNetworkSpecifierOpen(peerHandle);
+        WifiAwareNetworkSpecifier ns =
+                (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierOpen(
+                        peerHandle);
 
         // validate format
-        JSONObject jsonObject = new JSONObject(networkSpecifier);
-        collector.checkThat("role", role,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
-        collector.checkThat("client_id", clientId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
-        collector.checkThat("session_id", sessionId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
-        collector.checkThat("peer_id", peerHandle.peerId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
+        collector.checkThat("role", role, equalTo(ns.role));
+        collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+        collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
+        collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
 
         // (4) request an encrypted (PMK) network specifier from the session
-        networkSpecifier = publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+        ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPmk(
+                peerHandle, pmk);
 
         // validate format
-        jsonObject = new JSONObject(networkSpecifier);
-        collector.checkThat("role", role,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
-        collector.checkThat("client_id", clientId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
-        collector.checkThat("session_id", sessionId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
-        collector.checkThat("peer_id", peerHandle.peerId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
-        collector.checkThat("pmk", pmkB64 ,
-                equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PMK)));
+        collector.checkThat("role", role, equalTo(ns.role));
+        collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+        collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
+        collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
+        collector.checkThat("pmk", pmk , equalTo(ns.pmk));
 
         // (5) request an encrypted (Passphrase) network specifier from the session
-        networkSpecifier = publishSession.getValue().createNetworkSpecifierPassphrase(peerHandle,
-                passphrase);
+        ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPassphrase(
+                peerHandle, passphrase);
 
         // validate format
-        jsonObject = new JSONObject(networkSpecifier);
-        collector.checkThat("role", role,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
-        collector.checkThat("client_id", clientId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
-        collector.checkThat("session_id", sessionId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
-        collector.checkThat("peer_id", peerHandle.peerId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
-        collector.checkThat("passphrase", passphrase,
-                equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PASSPHRASE)));
+        collector.checkThat("role", role, equalTo(ns.role));
+        collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+        collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
+        collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
+        collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase));
 
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
                 mockPublishSession, mockRttListener);
@@ -1054,8 +1036,6 @@
         final byte[] pmk = "Some arbitrary pmk data".getBytes();
         final String passphrase = "A really bad password";
 
-        String pmkB64 = Base64.encodeToString(pmk, Base64.DEFAULT);
-
         ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
                 WifiAwareSession.class);
         ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
@@ -1074,47 +1054,32 @@
         WifiAwareSession session = sessionCaptor.getValue();
 
         // (2) request an open (unencrypted) direct network specifier
-        String networkSpecifier = session.createNetworkSpecifierOpen(role, someMac);
+        WifiAwareNetworkSpecifier ns =
+                (WifiAwareNetworkSpecifier) session.createNetworkSpecifierOpen(role, someMac);
 
         // validate format
-        JSONObject jsonObject = new JSONObject(networkSpecifier);
-        collector.checkThat("role", role,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
-        collector.checkThat("client_id", clientId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
-        collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
-                jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
-                false)));
+        collector.checkThat("role", role, equalTo(ns.role));
+        collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+        collector.checkThat("peer_mac", someMac, equalTo(ns.peerMac));
 
         // (3) request an encrypted (PMK) direct network specifier
-        networkSpecifier = session.createNetworkSpecifierPmk(role, someMac, pmk);
+        ns = (WifiAwareNetworkSpecifier) session.createNetworkSpecifierPmk(role, someMac, pmk);
 
         // validate format
-        jsonObject = new JSONObject(networkSpecifier);
-        collector.checkThat("role", role,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
-        collector.checkThat("client_id", clientId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
-        collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
-                jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
-                false)));
-        collector.checkThat("pmk", pmkB64,
-                equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PMK)));
+        collector.checkThat("role", role, equalTo(ns.role));
+        collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+        collector.checkThat("peer_mac", someMac, equalTo(ns.peerMac));
+        collector.checkThat("pmk", pmk, equalTo(ns.pmk));
 
         // (4) request an encrypted (Passphrase) direct network specifier
-        networkSpecifier = session.createNetworkSpecifierPassphrase(role, someMac, passphrase);
+        ns = (WifiAwareNetworkSpecifier) session.createNetworkSpecifierPassphrase(role, someMac,
+                passphrase);
 
         // validate format
-        jsonObject = new JSONObject(networkSpecifier);
-        collector.checkThat("role", role,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
-        collector.checkThat("client_id", clientId,
-                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
-        collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
-                jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
-                false)));
-        collector.checkThat("passphrase", passphrase,
-                equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PASSPHRASE)));
+        collector.checkThat("role", role, equalTo(ns.role));
+        collector.checkThat("client_id", clientId, equalTo(ns.clientId));
+        collector.checkThat("peer_mac", someMac, equalTo(ns.peerMac));
+        collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase));
 
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
                 mockPublishSession, mockRttListener);